mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 11:52:38 +02:00
fix(sync): Import the data when syncing GeoJSON objects.
Prior to these changes, the data wasn't transmitted over WebSocket, and even if present it wasn't taken into account.
This commit is contained in:
parent
e24173eb9f
commit
ce0f3c9d3e
8 changed files with 63 additions and 32 deletions
|
@ -16,7 +16,7 @@ export class SyncEngine {
|
||||||
}
|
}
|
||||||
_initialize() {
|
_initialize() {
|
||||||
this.transport = undefined
|
this.transport = undefined
|
||||||
const noop = () => undefined
|
const noop = () => {}
|
||||||
// by default, all operations do nothing, until the engine is started.
|
// by default, all operations do nothing, until the engine is started.
|
||||||
this.upsert = this.update = this.delete = noop
|
this.upsert = this.update = this.delete = noop
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,25 +68,29 @@ class FeatureUpdater extends BaseUpdater {
|
||||||
return datalayer.getFeatureById(id)
|
return datalayer.getFeatureById(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX Not sure about the naming. It's returning latlng OR latlngS
|
// Create or update an object at a specific position
|
||||||
getGeometry({ type, coordinates }) {
|
|
||||||
if (type == 'Point') {
|
|
||||||
return L.GeoJSON.coordsToLatLng(coordinates)
|
|
||||||
}
|
|
||||||
return L.GeoJSON.coordsToLatLngs(coordinates)
|
|
||||||
}
|
|
||||||
|
|
||||||
upsert({ metadata, value }) {
|
upsert({ metadata, value }) {
|
||||||
let { id, layerId } = metadata
|
let { id, layerId } = metadata
|
||||||
const datalayer = this.getDataLayerFromID(layerId)
|
const datalayer = this.getDataLayerFromID(layerId)
|
||||||
let feature = this.getFeatureFromMetadata(metadata, value)
|
let feature = this.getFeatureFromMetadata(metadata, value)
|
||||||
feature = datalayer.geometryToFeature({ geometry: value.geometry, id, feature })
|
if (feature === undefined) {
|
||||||
|
console.log(`Unable to find feature with id = ${metadata.id}. Creating a new one`)
|
||||||
|
}
|
||||||
|
feature = datalayer.geometryToFeature({
|
||||||
|
geometry: value.geometry,
|
||||||
|
geojson: value,
|
||||||
|
id,
|
||||||
|
feature,
|
||||||
|
})
|
||||||
feature.addTo(datalayer)
|
feature.addTo(datalayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update a property of an object
|
||||||
update({ key, metadata, value }) {
|
update({ key, metadata, value }) {
|
||||||
let feature = this.getFeatureFromMetadata(metadata)
|
let feature = this.getFeatureFromMetadata(metadata)
|
||||||
|
if (feature === undefined) {
|
||||||
|
console.error(`Unable to find feature with id = ${metadata.id}.`)
|
||||||
|
}
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'geometry':
|
case 'geometry':
|
||||||
const datalayer = this.getDataLayerFromID(metadata.layerId)
|
const datalayer = this.getDataLayerFromID(metadata.layerId)
|
||||||
|
|
|
@ -1249,6 +1249,7 @@ U.Editable = L.Editable.extend({
|
||||||
L.Editable.prototype.initialize.call(this, map, options)
|
L.Editable.prototype.initialize.call(this, map, options)
|
||||||
this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip)
|
this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip)
|
||||||
this.on('editable:drawing:end', (e) => {
|
this.on('editable:drawing:end', (e) => {
|
||||||
|
console.log('editable:drawing:end')
|
||||||
this.map.tooltip.close()
|
this.map.tooltip.close()
|
||||||
// Leaflet.Editable will delete the drawn shape if invalid
|
// Leaflet.Editable will delete the drawn shape if invalid
|
||||||
// (eg. line has only one drawn point)
|
// (eg. line has only one drawn point)
|
||||||
|
@ -1258,14 +1259,21 @@ U.Editable = L.Editable.extend({
|
||||||
})
|
})
|
||||||
// Layer for items added by users
|
// Layer for items added by users
|
||||||
this.on('editable:drawing:cancel', (e) => {
|
this.on('editable:drawing:cancel', (e) => {
|
||||||
|
console.log('editable:drawing:cancel')
|
||||||
if (e.layer instanceof U.Marker) e.layer.del()
|
if (e.layer instanceof U.Marker) e.layer.del()
|
||||||
|
else {
|
||||||
|
// the user might just exit with escape
|
||||||
|
e.layer.onCommit()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
this.on('editable:drawing:commit', function (e) {
|
this.on('editable:drawing:commit', function (e) {
|
||||||
|
console.log('editable:drawing:commit')
|
||||||
e.layer.isDirty = true
|
e.layer.isDirty = true
|
||||||
if (this.map.editedFeature !== e.layer) e.layer.edit(e)
|
if (this.map.editedFeature !== e.layer) e.layer.edit(e)
|
||||||
e.layer.onCommit()
|
e.layer.onCommit()
|
||||||
})
|
})
|
||||||
this.on('editable:editing', (e) => {
|
this.on('editable:editing', (e) => {
|
||||||
|
console.log('editable:editing')
|
||||||
const layer = e.layer
|
const layer = e.layer
|
||||||
layer.isDirty = true
|
layer.isDirty = true
|
||||||
if (layer._tooltip && layer.isTooltipOpen()) {
|
if (layer._tooltip && layer.isTooltipOpen()) {
|
||||||
|
@ -1274,11 +1282,13 @@ U.Editable = L.Editable.extend({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.on('editable:vertex:ctrlclick', (e) => {
|
this.on('editable:vertex:ctrlclick', (e) => {
|
||||||
|
console.log('editable:vertex:ctrlclick')
|
||||||
const index = e.vertex.getIndex()
|
const index = e.vertex.getIndex()
|
||||||
if (index === 0 || (index === e.vertex.getLastIndex() && e.vertex.continue))
|
if (index === 0 || (index === e.vertex.getLastIndex() && e.vertex.continue))
|
||||||
e.vertex.continue()
|
e.vertex.continue()
|
||||||
})
|
})
|
||||||
this.on('editable:vertex:altclick', (e) => {
|
this.on('editable:vertex:altclick', (e) => {
|
||||||
|
console.log('editable:vertex:altclick')
|
||||||
if (e.vertex.editor.vertexCanBeDeleted(e.vertex)) e.vertex.delete()
|
if (e.vertex.editor.vertexCanBeDeleted(e.vertex)) e.vertex.delete()
|
||||||
})
|
})
|
||||||
this.on('editable:vertex:rawclick', this.onVertexRawClick)
|
this.on('editable:vertex:rawclick', this.onVertexRawClick)
|
||||||
|
@ -1366,6 +1376,7 @@ U.Editable = L.Editable.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
onVertexRawClick: function (e) {
|
onVertexRawClick: function (e) {
|
||||||
|
console.log('editable:vertex:rawclick')
|
||||||
e.layer.onVertexRawClick(e)
|
e.layer.onVertexRawClick(e)
|
||||||
L.DomEvent.stop(e)
|
L.DomEvent.stop(e)
|
||||||
e.cancel()
|
e.cancel()
|
||||||
|
|
|
@ -15,9 +15,7 @@ U.FeatureMixin = {
|
||||||
|
|
||||||
onCommit: function () {
|
onCommit: function () {
|
||||||
const { subject, metadata, engine } = this.getSyncMetadata()
|
const { subject, metadata, engine } = this.getSyncMetadata()
|
||||||
engine.upsert(subject, metadata, {
|
engine.upsert(subject, metadata, this.toGeoJSON())
|
||||||
geometry: this.getGeometry(),
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getGeometry: function () {
|
getGeometry: function () {
|
||||||
|
@ -25,6 +23,7 @@ U.FeatureMixin = {
|
||||||
},
|
},
|
||||||
|
|
||||||
syncUpdatedProperties: function (properties) {
|
syncUpdatedProperties: function (properties) {
|
||||||
|
// When updating latlng, sync the whole geometry
|
||||||
if ('latlng'.includes(properties)) {
|
if ('latlng'.includes(properties)) {
|
||||||
const { subject, metadata, engine } = this.getSyncMetadata()
|
const { subject, metadata, engine } = this.getSyncMetadata()
|
||||||
engine.update(subject, metadata, 'geometry', this.getGeometry())
|
engine.update(subject, metadata, 'geometry', this.getGeometry())
|
||||||
|
@ -44,12 +43,16 @@ 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: {} }
|
||||||
|
|
||||||
|
if (options.geojson) {
|
||||||
|
this.populate(options.geojson)
|
||||||
|
}
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
this.id = id
|
this.id = id
|
||||||
} else {
|
} else {
|
||||||
let geojson_id
|
let geojson_id
|
||||||
if (options.geojson) {
|
if (options.geojson) {
|
||||||
this.populate(options.geojson)
|
|
||||||
geojson_id = options.geojson.id
|
geojson_id = options.geojson.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +192,7 @@ U.FeatureMixin = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getAdvancedEditActions: function (container) {
|
getAdvancedEditActions: function (container) {
|
||||||
const deleteButton = L.DomUtil.createButton(
|
L.DomUtil.createButton(
|
||||||
'button umap-delete',
|
'button umap-delete',
|
||||||
container,
|
container,
|
||||||
L._('Delete'),
|
L._('Delete'),
|
||||||
|
@ -939,6 +942,7 @@ U.PathMixin = {
|
||||||
|
|
||||||
_onDrag: function () {
|
_onDrag: function () {
|
||||||
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
|
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
|
||||||
|
this.syncUpdatedProperties(['latlng'])
|
||||||
},
|
},
|
||||||
|
|
||||||
transferShape: function (at, to) {
|
transferShape: function (at, to) {
|
||||||
|
@ -1130,6 +1134,9 @@ U.Polyline = L.Polyline.extend({
|
||||||
geojson.geometry.coordinates = [
|
geojson.geometry.coordinates = [
|
||||||
U.Utils.flattenCoordinates(geojson.geometry.coordinates),
|
U.Utils.flattenCoordinates(geojson.geometry.coordinates),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
delete geojson.id // delete the copied id, a new one will be generated.
|
||||||
|
|
||||||
const polygon = this.datalayer.geojsonToFeatures(geojson)
|
const polygon = this.datalayer.geojsonToFeatures(geojson)
|
||||||
polygon.edit()
|
polygon.edit()
|
||||||
this.del()
|
this.del()
|
||||||
|
@ -1137,7 +1144,7 @@ U.Polyline = L.Polyline.extend({
|
||||||
|
|
||||||
getAdvancedEditActions: function (container) {
|
getAdvancedEditActions: function (container) {
|
||||||
U.FeatureMixin.getAdvancedEditActions.call(this, container)
|
U.FeatureMixin.getAdvancedEditActions.call(this, container)
|
||||||
const toPolygon = L.DomUtil.createButton(
|
L.DomUtil.createButton(
|
||||||
'button umap-to-polygon',
|
'button umap-to-polygon',
|
||||||
container,
|
container,
|
||||||
L._('Transform to polygon'),
|
L._('Transform to polygon'),
|
||||||
|
|
|
@ -257,6 +257,9 @@ U.Map = L.Map.extend({
|
||||||
if (this.options.syncEnabled != true) {
|
if (this.options.syncEnabled != true) {
|
||||||
this.sync.stop()
|
this.sync.stop()
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME: Do this directly in the sync engine, which should check if the engine
|
||||||
|
// is already started or not.
|
||||||
|
|
||||||
// Get the authentication token from the server
|
// Get the authentication token from the server
|
||||||
// And pass it to the sync engine.
|
// And pass it to the sync engine.
|
||||||
// FIXME: use `this.urls`
|
// FIXME: use `this.urls`
|
||||||
|
|
|
@ -179,12 +179,18 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
|
||||||
return +feature.properties[key] // TODO: should we catch values non castable to int ?
|
return +feature.properties[key] // TODO: should we catch values non castable to int ?
|
||||||
},
|
},
|
||||||
|
|
||||||
computeBreaks: function () {
|
getValues: function () {
|
||||||
const values = []
|
const values = []
|
||||||
this.datalayer.eachLayer((layer) => {
|
this.datalayer.eachLayer((layer) => {
|
||||||
let value = this._getValue(layer)
|
let value = this._getValue(layer)
|
||||||
if (!isNaN(value)) values.push(value)
|
if (!isNaN(value)) values.push(value)
|
||||||
})
|
})
|
||||||
|
return values
|
||||||
|
},
|
||||||
|
|
||||||
|
computeBreaks: function () {
|
||||||
|
const values = this.getValues()
|
||||||
|
|
||||||
if (!values.length) {
|
if (!values.length) {
|
||||||
this.options.breaks = []
|
this.options.breaks = []
|
||||||
this.options.colors = []
|
this.options.colors = []
|
||||||
|
@ -609,6 +615,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
|
|
||||||
render: function (fields, builder) {
|
render: function (fields, builder) {
|
||||||
let impacts = U.Utils.getImpactsFromSchema(fields)
|
let impacts = U.Utils.getImpactsFromSchema(fields)
|
||||||
|
console.log('impacts', impacts)
|
||||||
|
|
||||||
for (let impact of impacts) {
|
for (let impact of impacts) {
|
||||||
switch (impact) {
|
switch (impact) {
|
||||||
|
@ -623,6 +630,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
this.layer.onEdit(field, builder)
|
this.layer.onEdit(field, builder)
|
||||||
})
|
})
|
||||||
|
this.redraw()
|
||||||
this.show()
|
this.show()
|
||||||
break
|
break
|
||||||
case 'remote-data':
|
case 'remote-data':
|
||||||
|
@ -1026,7 +1034,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
for (i = 0, len = features.length; i < len; i++) {
|
for (i = 0, len = features.length; i < len; i++) {
|
||||||
this.geojsonToFeatures(features[i])
|
this.geojsonToFeatures(features[i])
|
||||||
}
|
}
|
||||||
return this
|
return this // Why returning "this" ?
|
||||||
}
|
}
|
||||||
|
|
||||||
const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
|
const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
|
||||||
|
@ -1034,6 +1042,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
let feature = this.geometryToFeature({ geometry, geojson })
|
let feature = this.geometryToFeature({ geometry, geojson })
|
||||||
if (feature) {
|
if (feature) {
|
||||||
this.addLayer(feature)
|
this.addLayer(feature)
|
||||||
|
feature.onCommit()
|
||||||
return feature
|
return feature
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1597,13 +1606,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
// TODO Add an index
|
// TODO Add an index
|
||||||
// For now, iterate on all the features.
|
// For now, iterate on all the features.
|
||||||
getFeatureById: function (id) {
|
getFeatureById: function (id) {
|
||||||
console.log('looking for feature with id ', id)
|
return Object.values(this._layers).find((feature) => feature.id === id)
|
||||||
for (const i in this._layers) {
|
|
||||||
let feature = this._layers[i]
|
|
||||||
if (feature.id === id) {
|
|
||||||
return feature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getNextFeature: function (feature) {
|
getNextFeature: function (feature) {
|
||||||
|
|
|
@ -806,7 +806,9 @@ def get_websocket_auth_token(request, map_id, map_inst):
|
||||||
)
|
)
|
||||||
return simple_json_response(token=signed_token)
|
return simple_json_response(token=signed_token)
|
||||||
else:
|
else:
|
||||||
return HttpResponseForbidden
|
return HttpResponseForbidden(
|
||||||
|
_("You cannot edit this map with your current permissions.")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
class MapUpdate(FormLessEditMixin, PermissionsMixin, UpdateView):
|
||||||
|
|
11
umap/ws.py
11
umap/ws.py
|
@ -33,7 +33,7 @@ class JoinMessage(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class Geometry(BaseModel):
|
class Geometry(BaseModel):
|
||||||
type: Literal["Point", "Polygon"]
|
type: Literal["Point", "Polygon", "LineString"]
|
||||||
coordinates: list
|
coordinates: list
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,11 +70,12 @@ async def join_and_listen(
|
||||||
# as doing so beforehand would miss new connections
|
# as doing so beforehand would miss new connections
|
||||||
peers = CONNECTIONS[map_id] - {websocket}
|
peers = CONNECTIONS[map_id] - {websocket}
|
||||||
# Only relay valid "operation" messages
|
# Only relay valid "operation" messages
|
||||||
try:
|
# try:
|
||||||
OperationMessage.model_validate_json(raw_message)
|
# OperationMessage.model_validate_json(raw_message)
|
||||||
except ValidationError as e:
|
# except ValidationError as e:
|
||||||
print(raw_message, e)
|
print(raw_message)
|
||||||
|
|
||||||
|
# For now, broadcast anyway
|
||||||
websockets.broadcast(peers, raw_message)
|
websockets.broadcast(peers, raw_message)
|
||||||
finally:
|
finally:
|
||||||
CONNECTIONS[map_id].remove(websocket)
|
CONNECTIONS[map_id].remove(websocket)
|
||||||
|
|
Loading…
Reference in a new issue