Merge pull request #1649 from umap-project/add-features-ids
chore: add ids on features
This commit is contained in:
commit
fa0208519e
4 changed files with 97 additions and 1 deletions
|
@ -1,10 +1,21 @@
|
||||||
import * as L from '../../vendors/leaflet/leaflet-src.esm.js'
|
import * as L from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
import URLs from './urls.js'
|
import URLs from './urls.js'
|
||||||
import Browser from './browser.js'
|
import Browser from './browser.js'
|
||||||
|
import * as Utils from './utils.js'
|
||||||
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'
|
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'
|
||||||
|
|
||||||
// Import modules and export them to the global scope.
|
// Import modules and export them to the global scope.
|
||||||
// For the not yet module-compatible JS out there.
|
// For the not yet module-compatible JS out there.
|
||||||
|
|
||||||
// Copy the leaflet module, it's expected by leaflet plugins to be writeable.
|
// Copy the leaflet module, it's expected by leaflet plugins to be writeable.
|
||||||
window.L = { ...L }
|
window.L = { ...L }
|
||||||
window.U = { URLs, Request, ServerRequest, RequestError, HTTPError, NOKError, Browser }
|
window.U = {
|
||||||
|
URLs,
|
||||||
|
Request,
|
||||||
|
ServerRequest,
|
||||||
|
RequestError,
|
||||||
|
HTTPError,
|
||||||
|
NOKError,
|
||||||
|
Browser,
|
||||||
|
Utils,
|
||||||
|
}
|
||||||
|
|
24
umap/static/umap/js/modules/utils.js
Normal file
24
umap/static/umap/js/modules/utils.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Generate a pseudo-unique identifier (5 chars long, mixed-case alphanumeric)
|
||||||
|
*
|
||||||
|
* Here's the collision risk:
|
||||||
|
* - for 6 chars, 1 in 100 000
|
||||||
|
* - for 5 chars, 5 in 100 000
|
||||||
|
* - for 4 chars, 500 in 100 000
|
||||||
|
*
|
||||||
|
* @returns string
|
||||||
|
*/
|
||||||
|
export function generateId() {
|
||||||
|
return btoa(Math.random().toString()).substring(10, 15)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the ID matches the expected format.
|
||||||
|
*
|
||||||
|
* @param {string} string
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function checkId(string) {
|
||||||
|
if (typeof string !== 'string') return false
|
||||||
|
return /^[A-Za-z0-9]{5}$/.test(string)
|
||||||
|
}
|
|
@ -9,8 +9,17 @@ U.FeatureMixin = {
|
||||||
// DataLayer the marker belongs to
|
// DataLayer the marker belongs to
|
||||||
this.datalayer = options.datalayer || null
|
this.datalayer = options.datalayer || null
|
||||||
this.properties = { _umap_options: {} }
|
this.properties = { _umap_options: {} }
|
||||||
|
let geojson_id
|
||||||
if (options.geojson) {
|
if (options.geojson) {
|
||||||
this.populate(options.geojson)
|
this.populate(options.geojson)
|
||||||
|
geojson_id = options.geojson.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each feature needs an unique identifier
|
||||||
|
if (U.Utils.checkId(geojson_id)) {
|
||||||
|
this.id = geojson_id
|
||||||
|
} else {
|
||||||
|
this.id = U.Utils.generateId()
|
||||||
}
|
}
|
||||||
let isDirty = false
|
let isDirty = false
|
||||||
const self = this
|
const self = this
|
||||||
|
@ -344,6 +353,7 @@ U.FeatureMixin = {
|
||||||
toGeoJSON: function () {
|
toGeoJSON: function () {
|
||||||
const geojson = this.parentClass.prototype.toGeoJSON.call(this)
|
const geojson = this.parentClass.prototype.toGeoJSON.call(this)
|
||||||
geojson.properties = this.cloneProperties()
|
geojson.properties = this.cloneProperties()
|
||||||
|
geojson.id = this.id
|
||||||
delete geojson.properties._storage_options
|
delete geojson.properties._storage_options
|
||||||
return geojson
|
return geojson
|
||||||
},
|
},
|
||||||
|
|
51
umap/tests/integration/test_features_id_generation.py
Normal file
51
umap/tests/integration/test_features_id_generation.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_ids_generation(page, live_server, tilelayer):
|
||||||
|
page.goto(f"{live_server.url}/en/map/new/")
|
||||||
|
|
||||||
|
# Click on the Draw a line button on a new map.
|
||||||
|
create_polyline = page.locator(".leaflet-control-toolbar ").get_by_title(
|
||||||
|
"Draw a polyline"
|
||||||
|
)
|
||||||
|
create_polyline.click()
|
||||||
|
|
||||||
|
map = page.locator("#map")
|
||||||
|
map.click(position={"x": 200, "y": 200})
|
||||||
|
map.click(position={"x": 100, "y": 100})
|
||||||
|
# Click again to finish
|
||||||
|
map.click(position={"x": 100, "y": 100})
|
||||||
|
|
||||||
|
# Click on the Draw a polygon button on a new map.
|
||||||
|
create_polygon = page.locator(".leaflet-control-toolbar ").get_by_title(
|
||||||
|
"Draw a polygon"
|
||||||
|
)
|
||||||
|
create_polygon.click()
|
||||||
|
|
||||||
|
map = page.locator("#map")
|
||||||
|
map.click(position={"x": 300, "y": 300})
|
||||||
|
map.click(position={"x": 300, "y": 400})
|
||||||
|
map.click(position={"x": 350, "y": 450})
|
||||||
|
# Click again to finish
|
||||||
|
map.click(position={"x": 350, "y": 450})
|
||||||
|
|
||||||
|
download_panel = page.get_by_title("Share and download")
|
||||||
|
download_panel.click()
|
||||||
|
|
||||||
|
button = page.get_by_role("button", name="geojson")
|
||||||
|
|
||||||
|
with page.expect_download() as download_info:
|
||||||
|
button.click()
|
||||||
|
|
||||||
|
download = download_info.value
|
||||||
|
|
||||||
|
path = Path("/tmp/") / download.suggested_filename
|
||||||
|
download.save_as(path)
|
||||||
|
downloaded = json.loads(path.read_text())
|
||||||
|
|
||||||
|
assert "features" in downloaded
|
||||||
|
features = downloaded["features"]
|
||||||
|
assert len(features) == 2
|
||||||
|
assert "id" in features[0]
|
||||||
|
assert "id" in features[1]
|
Loading…
Reference in a new issue