mirror of
https://github.com/umap-project/umap.git
synced 2025-05-06 06:21:49 +02:00
Use GeoJSON in the WS protocol
This commit is contained in:
parent
5b0bc265a6
commit
88c0dac6d3
4 changed files with 155 additions and 49 deletions
|
@ -1,5 +1,5 @@
|
|||
import { WebSocketTransport } from "./websocket.js"
|
||||
import { MapUpdater, FeatureUpdater } from "./updaters.js"
|
||||
import { MapUpdater, MarkerUpdater, PolygonUpdater, PolylineUpdater } from "./updaters.js"
|
||||
|
||||
export class SyncEngine {
|
||||
constructor(map) {
|
||||
|
@ -7,7 +7,7 @@ export class SyncEngine {
|
|||
this.transport = new WebSocketTransport(this.receiver)
|
||||
this.sender = new MessagesSender(this.transport)
|
||||
|
||||
this.create = this.sender.create.bind(this.sender)
|
||||
this.upsert = this.sender.upsert.bind(this.sender)
|
||||
this.update = this.sender.update.bind(this.sender)
|
||||
this.delete = this.sender.delete.bind(this.sender)
|
||||
}
|
||||
|
@ -18,21 +18,33 @@ export class MessagesDispatcher {
|
|||
this.map = map
|
||||
this.updaters = {
|
||||
map: new MapUpdater(this.map),
|
||||
feature: new FeatureUpdater(this.map)
|
||||
marker: new MarkerUpdater(this.map),
|
||||
polyline: new PolylineUpdater(this.map),
|
||||
polygon: new PolygonUpdater(this.map),
|
||||
}
|
||||
}
|
||||
|
||||
getUpdater(subject) {
|
||||
if (["map", "feature"].includes(subject)) {
|
||||
return this.updaters[subject]
|
||||
getUpdater(subject, metadata) {
|
||||
switch (subject) {
|
||||
case 'feature':
|
||||
const featureTypeExists = Object.keys(this.updaters).includes(metadata.featureType)
|
||||
if (featureTypeExists) {
|
||||
const updater = this.updaters[metadata.featureType]
|
||||
console.log(`found updater ${metadata.featureType}, ${updater}`)
|
||||
return updater
|
||||
}
|
||||
case 'map':
|
||||
return this.updaters[subject]
|
||||
default:
|
||||
throw new Error(`Unknown updater ${subject}, ${metadata}`)
|
||||
}
|
||||
throw new Error(`Unknown updater ${subject}`)
|
||||
|
||||
}
|
||||
|
||||
dispatch({ kind, payload }) {
|
||||
console.log(kind, payload)
|
||||
if (kind == "sync-protocol") {
|
||||
let updater = this.getUpdater(payload.subject)
|
||||
let updater = this.getUpdater(payload.subject, payload.metadata)
|
||||
updater.applyMessage(payload)
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +67,8 @@ export class MessagesSender {
|
|||
this._transport.send("sync-protocol", message)
|
||||
}
|
||||
|
||||
create(subject, metadata, value) {
|
||||
this.send({ verb: "create", subject, metadata, value })
|
||||
upsert(subject, metadata, value) {
|
||||
this.send({ verb: "upsert", subject, metadata, value })
|
||||
}
|
||||
|
||||
update(subject, metadata, key, value) {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* This file contains the updaters: classes that are able to convert messages
|
||||
* received from another party (or the server) to changes on the map.
|
||||
*/
|
||||
|
||||
class BaseUpdater {
|
||||
updateObjectValue(obj, key, value) {
|
||||
// XXX refactor so it's cleaner
|
||||
|
@ -36,8 +41,16 @@ export class MapUpdater extends BaseUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
// Maybe have an updater per type of feature (marker, polyline, etc)
|
||||
export class FeatureUpdater extends BaseUpdater {
|
||||
/**
|
||||
* This is an abstract base class
|
||||
* And needs to be subclassed to be used.
|
||||
*
|
||||
* The child classes need to expose:
|
||||
* - `featureClass`: the name of the class to create the feature
|
||||
* - `featureArgument`: an object with the properties to pass to the class when bulding it.
|
||||
**/
|
||||
class FeatureUpdater extends BaseUpdater {
|
||||
|
||||
constructor(map) {
|
||||
super()
|
||||
this.map = map
|
||||
|
@ -54,29 +67,65 @@ export class FeatureUpdater extends BaseUpdater {
|
|||
return datalayer.getFeatureById(id)
|
||||
}
|
||||
|
||||
create({ metadata, value }) {
|
||||
// XXX Not sure about the naming. It's returning latlng OR latlngS
|
||||
getGeometry({ type, coordinates }) {
|
||||
if (type == "Point") {
|
||||
return L.GeoJSON.coordsToLatLng(coordinates)
|
||||
}
|
||||
return L.GeoJSON.coordsToLatLngs(coordinates)
|
||||
}
|
||||
|
||||
upsert({ metadata, value }) {
|
||||
let { id, layerId } = metadata
|
||||
const datalayer = this.getLayerFromID(layerId)
|
||||
let marker = new L.U.Marker(this.map, value.latlng, { datalayer }, id)
|
||||
marker.addTo(datalayer)
|
||||
let feature = this.getFeatureFromMetadata(metadata, value)
|
||||
feature = datalayer.geometryToFeature({ geometry: value.geometry, id, feature })
|
||||
feature.addTo(datalayer)
|
||||
}
|
||||
|
||||
update({ key, metadata, value }) {
|
||||
let feature = this.getFeatureFromMetadata(metadata)
|
||||
|
||||
if (key == "latlng") {
|
||||
feature.setLatLng(value)
|
||||
} else {
|
||||
this.updateObjectValue(feature, key, value)
|
||||
switch (key) {
|
||||
case "geometry":
|
||||
const datalayer = this.getLayerFromID(metadata.layerId)
|
||||
datalayer.geometryToFeature({ geometry: value, id: metadata.id, feature })
|
||||
default:
|
||||
this.updateObjectValue(feature, key, value)
|
||||
}
|
||||
|
||||
feature.datalayer.indexProperties(feature)
|
||||
feature.renderProperties([key])
|
||||
}
|
||||
|
||||
delete({ metadata }) {
|
||||
// XXX
|
||||
// We need to distinguish between properties getting deleted
|
||||
// XXX Distinguish between properties getting deleted
|
||||
// and the wole feature getting deleted
|
||||
let feature = this.getFeatureFromMetadata(metadata)
|
||||
feature.del()
|
||||
if (feature) feature.del()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PathUpdater extends FeatureUpdater {
|
||||
}
|
||||
|
||||
class MarkerUpdater extends FeatureUpdater {
|
||||
featureType = 'marker'
|
||||
featureClass = L.U.Marker
|
||||
featureArgument = 'latlng'
|
||||
}
|
||||
|
||||
|
||||
class PolygonUpdater extends PathUpdater {
|
||||
featureType = 'polygon'
|
||||
featureClass = L.U.Polygon
|
||||
featureArgument = 'latlngs'
|
||||
}
|
||||
|
||||
class PolylineUpdater extends PathUpdater {
|
||||
featureType = 'polyline'
|
||||
featureClass = L.U.Polyline
|
||||
featureArgument = 'latlngs'
|
||||
}
|
||||
|
||||
export { MarkerUpdater, PolygonUpdater, PolylineUpdater }
|
|
@ -25,12 +25,22 @@ L.U.FeatureMixin = {
|
|||
|
||||
onCommit: function () {
|
||||
console.log("onCommit")
|
||||
this.map.syncEngine.create(
|
||||
// We cannot make a difference between a creation
|
||||
// and an edition, so we use the "upsert" verb.
|
||||
|
||||
// The updater will lookup for already existing objects with the given ID
|
||||
// before creating a new one.
|
||||
|
||||
this.map.syncEngine.upsert(
|
||||
this.getSyncSubject(),
|
||||
this.getSyncMetadata(),
|
||||
{
|
||||
'latlng': this._latlng
|
||||
})
|
||||
{ 'geometry': this.getGeometry() })
|
||||
|
||||
},
|
||||
|
||||
// Sync-related methods
|
||||
getGeometry: function () {
|
||||
return this.toGeoJSON().geometry
|
||||
},
|
||||
|
||||
updateProperties: function (properties) {
|
||||
|
@ -38,7 +48,7 @@ L.U.FeatureMixin = {
|
|||
let metadata = this.getSyncMetadata()
|
||||
|
||||
if ('latlng'.includes(properties)) {
|
||||
this.map.syncEngine.update(subject, metadata, 'latlng', this._latlng)
|
||||
this.map.syncEngine.update(subject, metadata, 'geometry', this.getGeometry())
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -798,6 +808,26 @@ L.U.Marker = L.Marker.extend({
|
|||
})
|
||||
|
||||
L.U.PathMixin = {
|
||||
|
||||
/* getGeometry: function () {
|
||||
// Clean the latlngs to avoid private properties to be carried away
|
||||
console.log("getLatlngs", this.getLatLngs())
|
||||
// latlngs can be an array of latlng, but also arrays of arrays.
|
||||
// Remove the extra object properties
|
||||
let _cleanLatLngs = function (item) {
|
||||
if ('lat' in item) {
|
||||
let { lat, lng } = item
|
||||
return { lat, lng }
|
||||
}
|
||||
return item.map(_cleanLatLngs)
|
||||
}
|
||||
const cleaned_latlngs = this.getLatLngs()//.map(_cleanLatLngs)
|
||||
console.log("cleaned latlngs", cleaned_latlngs)
|
||||
return {
|
||||
'latlngs': cleaned_latlngs
|
||||
}
|
||||
}, */
|
||||
|
||||
hasGeom: function () {
|
||||
return !this.isEmpty()
|
||||
},
|
||||
|
|
|
@ -961,8 +961,6 @@ L.U.DataLayer = L.Evented.extend({
|
|||
const features = geojson instanceof Array ? geojson : geojson.features
|
||||
let i
|
||||
let len
|
||||
let latlng
|
||||
let latlngs
|
||||
|
||||
if (features) {
|
||||
L.Util.sortFeatures(features, this.map.getOption('sortKey'))
|
||||
|
@ -971,12 +969,23 @@ L.U.DataLayer = L.Evented.extend({
|
|||
}
|
||||
return this
|
||||
}
|
||||
|
||||
const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
|
||||
|
||||
let feature = this.geometryToFeature({ geometry, geojson })
|
||||
if (feature) {
|
||||
this.addLayer(feature)
|
||||
return feature
|
||||
}
|
||||
},
|
||||
|
||||
geometryToFeature: function ({ geometry, geojson = null, id = null, feature = null } = {}) {
|
||||
if (!geometry) return // null geometry is valid geojson.
|
||||
const coords = geometry.coordinates
|
||||
let layer
|
||||
let tmp
|
||||
let latlngs
|
||||
let latlng
|
||||
|
||||
// Create a default geojson if none is provided
|
||||
geojson ??= { type: "Feature", geometry: geometry }
|
||||
|
||||
switch (geometry.type) {
|
||||
case 'Point':
|
||||
|
@ -986,8 +995,11 @@ L.U.DataLayer = L.Evented.extend({
|
|||
console.error('Invalid latlng object from', coords)
|
||||
break
|
||||
}
|
||||
layer = this._pointToLayer(geojson, latlng)
|
||||
break
|
||||
if (feature) {
|
||||
feature.setLatLng(latlng)
|
||||
return feature
|
||||
}
|
||||
return this._pointToLayer(geojson, latlng, id)
|
||||
|
||||
case 'MultiLineString':
|
||||
case 'LineString':
|
||||
|
@ -996,14 +1008,21 @@ L.U.DataLayer = L.Evented.extend({
|
|||
geometry.type === 'LineString' ? 0 : 1
|
||||
)
|
||||
if (!latlngs.length) break
|
||||
layer = this._lineToLayer(geojson, latlngs)
|
||||
break
|
||||
if (feature) {
|
||||
feature.setLatLngs(latlngs)
|
||||
return feature
|
||||
}
|
||||
return this._lineToLayer(geojson, latlngs, id)
|
||||
|
||||
case 'MultiPolygon':
|
||||
case 'Polygon':
|
||||
latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
|
||||
layer = this._polygonToLayer(geojson, latlngs)
|
||||
break
|
||||
if (feature) {
|
||||
feature.setLatLngs(latlngs)
|
||||
return feature
|
||||
}
|
||||
return this._polygonToLayer(geojson, latlngs, id)
|
||||
|
||||
case 'GeometryCollection':
|
||||
return this.geojsonToFeatures(geometry.geometries)
|
||||
|
||||
|
@ -1015,30 +1034,26 @@ L.U.DataLayer = L.Evented.extend({
|
|||
level: 'error',
|
||||
})
|
||||
}
|
||||
if (layer) {
|
||||
this.addLayer(layer)
|
||||
return layer
|
||||
}
|
||||
},
|
||||
|
||||
_pointToLayer: function (geojson, latlng) {
|
||||
return new L.U.Marker(this.map, latlng, { geojson: geojson, datalayer: this })
|
||||
_pointToLayer: function (geojson, latlng, id) {
|
||||
return new L.U.Marker(this.map, latlng, { geojson: geojson, datalayer: this }, id)
|
||||
},
|
||||
|
||||
_lineToLayer: function (geojson, latlngs) {
|
||||
_lineToLayer: function (geojson, latlngs, id) {
|
||||
return new L.U.Polyline(this.map, latlngs, {
|
||||
geojson: geojson,
|
||||
datalayer: this,
|
||||
color: null,
|
||||
})
|
||||
}, id)
|
||||
},
|
||||
|
||||
_polygonToLayer: function (geojson, latlngs) {
|
||||
_polygonToLayer: function (geojson, latlngs, id) {
|
||||
// Ensure no empty hole
|
||||
// for (let i = latlngs.length - 1; i > 0; i--) {
|
||||
// if (!latlngs.slice()[i].length) latlngs.splice(i, 1);
|
||||
// }
|
||||
return new L.U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this })
|
||||
return new L.U.Polygon(this.map, latlngs, { geojson: geojson, datalayer: this }, id)
|
||||
},
|
||||
|
||||
importRaw: function (raw, type) {
|
||||
|
|
Loading…
Reference in a new issue