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 { WebSocketTransport } from "./websocket.js"
|
||||||
import { MapUpdater, FeatureUpdater } from "./updaters.js"
|
import { MapUpdater, MarkerUpdater, PolygonUpdater, PolylineUpdater } from "./updaters.js"
|
||||||
|
|
||||||
export class SyncEngine {
|
export class SyncEngine {
|
||||||
constructor(map) {
|
constructor(map) {
|
||||||
|
@ -7,7 +7,7 @@ export class SyncEngine {
|
||||||
this.transport = new WebSocketTransport(this.receiver)
|
this.transport = new WebSocketTransport(this.receiver)
|
||||||
this.sender = new MessagesSender(this.transport)
|
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.update = this.sender.update.bind(this.sender)
|
||||||
this.delete = this.sender.delete.bind(this.sender)
|
this.delete = this.sender.delete.bind(this.sender)
|
||||||
}
|
}
|
||||||
|
@ -18,21 +18,33 @@ export class MessagesDispatcher {
|
||||||
this.map = map
|
this.map = map
|
||||||
this.updaters = {
|
this.updaters = {
|
||||||
map: new MapUpdater(this.map),
|
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) {
|
getUpdater(subject, metadata) {
|
||||||
if (["map", "feature"].includes(subject)) {
|
switch (subject) {
|
||||||
return this.updaters[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
|
||||||
}
|
}
|
||||||
throw new Error(`Unknown updater ${subject}`)
|
case 'map':
|
||||||
|
return this.updaters[subject]
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown updater ${subject}, ${metadata}`)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({ kind, payload }) {
|
dispatch({ kind, payload }) {
|
||||||
console.log(kind, payload)
|
console.log(kind, payload)
|
||||||
if (kind == "sync-protocol") {
|
if (kind == "sync-protocol") {
|
||||||
let updater = this.getUpdater(payload.subject)
|
let updater = this.getUpdater(payload.subject, payload.metadata)
|
||||||
updater.applyMessage(payload)
|
updater.applyMessage(payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,8 +67,8 @@ export class MessagesSender {
|
||||||
this._transport.send("sync-protocol", message)
|
this._transport.send("sync-protocol", message)
|
||||||
}
|
}
|
||||||
|
|
||||||
create(subject, metadata, value) {
|
upsert(subject, metadata, value) {
|
||||||
this.send({ verb: "create", subject, metadata, value })
|
this.send({ verb: "upsert", subject, metadata, value })
|
||||||
}
|
}
|
||||||
|
|
||||||
update(subject, metadata, key, 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 {
|
class BaseUpdater {
|
||||||
updateObjectValue(obj, key, value) {
|
updateObjectValue(obj, key, value) {
|
||||||
// XXX refactor so it's cleaner
|
// 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) {
|
constructor(map) {
|
||||||
super()
|
super()
|
||||||
this.map = map
|
this.map = map
|
||||||
|
@ -54,29 +67,65 @@ export class FeatureUpdater extends BaseUpdater {
|
||||||
return datalayer.getFeatureById(id)
|
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
|
let { id, layerId } = metadata
|
||||||
const datalayer = this.getLayerFromID(layerId)
|
const datalayer = this.getLayerFromID(layerId)
|
||||||
let marker = new L.U.Marker(this.map, value.latlng, { datalayer }, id)
|
let feature = this.getFeatureFromMetadata(metadata, value)
|
||||||
marker.addTo(datalayer)
|
feature = datalayer.geometryToFeature({ geometry: value.geometry, id, feature })
|
||||||
|
feature.addTo(datalayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ key, metadata, value }) {
|
update({ key, metadata, value }) {
|
||||||
let feature = this.getFeatureFromMetadata(metadata)
|
let feature = this.getFeatureFromMetadata(metadata)
|
||||||
|
|
||||||
if (key == "latlng") {
|
switch (key) {
|
||||||
feature.setLatLng(value)
|
case "geometry":
|
||||||
} else {
|
const datalayer = this.getLayerFromID(metadata.layerId)
|
||||||
|
datalayer.geometryToFeature({ geometry: value, id: metadata.id, feature })
|
||||||
|
default:
|
||||||
this.updateObjectValue(feature, key, value)
|
this.updateObjectValue(feature, key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feature.datalayer.indexProperties(feature)
|
||||||
feature.renderProperties([key])
|
feature.renderProperties([key])
|
||||||
}
|
}
|
||||||
|
|
||||||
delete({ metadata }) {
|
delete({ metadata }) {
|
||||||
// XXX
|
// XXX Distinguish between properties getting deleted
|
||||||
// We need to distinguish between properties getting deleted
|
|
||||||
// and the wole feature getting deleted
|
// and the wole feature getting deleted
|
||||||
let feature = this.getFeatureFromMetadata(metadata)
|
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 () {
|
onCommit: function () {
|
||||||
console.log("onCommit")
|
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.getSyncSubject(),
|
||||||
this.getSyncMetadata(),
|
this.getSyncMetadata(),
|
||||||
{
|
{ 'geometry': this.getGeometry() })
|
||||||
'latlng': this._latlng
|
|
||||||
})
|
},
|
||||||
|
|
||||||
|
// Sync-related methods
|
||||||
|
getGeometry: function () {
|
||||||
|
return this.toGeoJSON().geometry
|
||||||
},
|
},
|
||||||
|
|
||||||
updateProperties: function (properties) {
|
updateProperties: function (properties) {
|
||||||
|
@ -38,7 +48,7 @@ L.U.FeatureMixin = {
|
||||||
let metadata = this.getSyncMetadata()
|
let metadata = this.getSyncMetadata()
|
||||||
|
|
||||||
if ('latlng'.includes(properties)) {
|
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 = {
|
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 () {
|
hasGeom: function () {
|
||||||
return !this.isEmpty()
|
return !this.isEmpty()
|
||||||
},
|
},
|
||||||
|
|
|
@ -961,8 +961,6 @@ L.U.DataLayer = L.Evented.extend({
|
||||||
const features = geojson instanceof Array ? geojson : geojson.features
|
const features = geojson instanceof Array ? geojson : geojson.features
|
||||||
let i
|
let i
|
||||||
let len
|
let len
|
||||||
let latlng
|
|
||||||
let latlngs
|
|
||||||
|
|
||||||
if (features) {
|
if (features) {
|
||||||
L.Util.sortFeatures(features, this.map.getOption('sortKey'))
|
L.Util.sortFeatures(features, this.map.getOption('sortKey'))
|
||||||
|
@ -971,12 +969,23 @@ L.U.DataLayer = L.Evented.extend({
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
const geometry = geojson.type === 'Feature' ? geojson.geometry : geojson
|
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.
|
if (!geometry) return // null geometry is valid geojson.
|
||||||
const coords = geometry.coordinates
|
const coords = geometry.coordinates
|
||||||
let layer
|
let latlngs
|
||||||
let tmp
|
let latlng
|
||||||
|
|
||||||
|
// Create a default geojson if none is provided
|
||||||
|
geojson ??= { type: "Feature", geometry: geometry }
|
||||||
|
|
||||||
switch (geometry.type) {
|
switch (geometry.type) {
|
||||||
case 'Point':
|
case 'Point':
|
||||||
|
@ -986,8 +995,11 @@ L.U.DataLayer = L.Evented.extend({
|
||||||
console.error('Invalid latlng object from', coords)
|
console.error('Invalid latlng object from', coords)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
layer = this._pointToLayer(geojson, latlng)
|
if (feature) {
|
||||||
break
|
feature.setLatLng(latlng)
|
||||||
|
return feature
|
||||||
|
}
|
||||||
|
return this._pointToLayer(geojson, latlng, id)
|
||||||
|
|
||||||
case 'MultiLineString':
|
case 'MultiLineString':
|
||||||
case 'LineString':
|
case 'LineString':
|
||||||
|
@ -996,14 +1008,21 @@ L.U.DataLayer = L.Evented.extend({
|
||||||
geometry.type === 'LineString' ? 0 : 1
|
geometry.type === 'LineString' ? 0 : 1
|
||||||
)
|
)
|
||||||
if (!latlngs.length) break
|
if (!latlngs.length) break
|
||||||
layer = this._lineToLayer(geojson, latlngs)
|
if (feature) {
|
||||||
break
|
feature.setLatLngs(latlngs)
|
||||||
|
return feature
|
||||||
|
}
|
||||||
|
return this._lineToLayer(geojson, latlngs, id)
|
||||||
|
|
||||||
case 'MultiPolygon':
|
case 'MultiPolygon':
|
||||||
case 'Polygon':
|
case 'Polygon':
|
||||||
latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
|
latlngs = L.GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
|
||||||
layer = this._polygonToLayer(geojson, latlngs)
|
if (feature) {
|
||||||
break
|
feature.setLatLngs(latlngs)
|
||||||
|
return feature
|
||||||
|
}
|
||||||
|
return this._polygonToLayer(geojson, latlngs, id)
|
||||||
|
|
||||||
case 'GeometryCollection':
|
case 'GeometryCollection':
|
||||||
return this.geojsonToFeatures(geometry.geometries)
|
return this.geojsonToFeatures(geometry.geometries)
|
||||||
|
|
||||||
|
@ -1015,30 +1034,26 @@ L.U.DataLayer = L.Evented.extend({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (layer) {
|
|
||||||
this.addLayer(layer)
|
|
||||||
return layer
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_pointToLayer: function (geojson, latlng) {
|
_pointToLayer: function (geojson, latlng, id) {
|
||||||
return new L.U.Marker(this.map, latlng, { geojson: geojson, datalayer: this })
|
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, {
|
return new L.U.Polyline(this.map, latlngs, {
|
||||||
geojson: geojson,
|
geojson: geojson,
|
||||||
datalayer: this,
|
datalayer: this,
|
||||||
color: null,
|
color: null,
|
||||||
})
|
}, id)
|
||||||
},
|
},
|
||||||
|
|
||||||
_polygonToLayer: function (geojson, latlngs) {
|
_polygonToLayer: function (geojson, latlngs, id) {
|
||||||
// Ensure no empty hole
|
// Ensure no empty hole
|
||||||
// for (let i = latlngs.length - 1; i > 0; i--) {
|
// for (let i = latlngs.length - 1; i > 0; i--) {
|
||||||
// if (!latlngs.slice()[i].length) latlngs.splice(i, 1);
|
// 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) {
|
importRaw: function (raw, type) {
|
||||||
|
|
Loading…
Reference in a new issue