From 3a81284b23ba41ac73947eed5b0dc87434ab6167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 07:31:30 +0200 Subject: [PATCH 01/20] Updated `package-lock.json` file --- package-lock.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4206e9e..e35bd52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,18 @@ { "name": "leaflet-geosse", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "leaflet-geosse", - "version": "1.0.0", + "version": "1.0.1", "license": "MIT", "devDependencies": { "uglify-es": ">=3.0.2" + }, + "peerDependencies": { + "leaflet": "^1.0.0" } }, "node_modules/commander": { @@ -18,6 +21,12 @@ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -51,6 +60,12 @@ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, + "leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", From b6c3897eacbb0002a170fcac954969c7346883d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 07:36:56 +0200 Subject: [PATCH 02/20] Code clean-up --- src/Leaflet.GeoSSE.src.js | 271 ++++++++++++++++++++------------------ 1 file changed, 140 insertions(+), 131 deletions(-) diff --git a/src/Leaflet.GeoSSE.src.js b/src/Leaflet.GeoSSE.src.js index e037a38..e9403fa 100644 --- a/src/Leaflet.GeoSSE.src.js +++ b/src/Leaflet.GeoSSE.src.js @@ -1,154 +1,161 @@ "use strict"; -var GeoSSE = L.GeoJSON.extend({ - /* - * Feature Layer class used to handle real-time reloading of - * geojson layers via server sent events. - * - * Extends L.GeoJSON class. + +/** + * Feature Layer class used to handle real-time reloading of geojson layers via + * server sent events. + * + * Extends L.GeoJSON class. + */ +const GeoSSE = L.GeoJSON.extend({ + /** + * Establishes connection to the event server and subscribes to the event + * stream. */ connectToEventStream: function () { - /* - * Establishes connection to the event server and subscribes to the event stream. + /** + * On create events, simply add the data. The expected data sent this event + * is a geojson feature. */ + function createEvent(event) { + const geojson = JSON.parse(event.data); - let cls = this; + self.addData(geojson); + } + + /** + * On update events, replace the existing feature based on featureId. The + * expected data sent by this event is a single geojson feature. + */ + function updateEvent(event) { + const geojson = JSON.parse(event.data); + + for (let l of self.getLayers()) { + if ( + l.feature.properties[featureIdField] === + geojson.properties[featureIdField] + ) { + self.removeLayer(l); + self.addData(geojson); + } + } + } + + /** + * On delete events, remove the existing feature based on featureId. The + * expected data sent by this event is a single geojson feature. + */ + function deleteEvent(event) { + const geojson = JSON.parse(event.data); + + for (let l of self.getLayers()) { + if ( + l.feature.properties[featureIdField] === + geojson.properties[featureIdField] + ) { + self.removeLayer(l); + } + } + } + + const self = this; + const {featureIdField} = this.options; if (typeof this.options.streamUrl === "undefined") { - // throw an error if no streamUrl is provided in options during initialization + // throw an error if no streamUrl is provided in options during + // initialization throw Error("Undefined event streamUrl."); - } else if (typeof this.options.featureIdField === "undefined") { - throw Error("Undefined featureIdField option."); - } else { - // set stream source - let source = new EventSource(this.options.streamUrl); - - source.addEventListener( - "create", - function createEvent(event) { - /* - * On create events, simply add the data. The expected data sent by this event is a - * geojson feature. - */ - let geojson = JSON.parse(event.data); - cls.addData(geojson); - }, - false - ); - - source.addEventListener( - "update", - function updateEvent(event) { - /* - * On update events, replace the existing feature based on featureId. The expected data sent by - * this event is a single geojson feature. - */ - let geojson = JSON.parse(event.data); - for (let l of cls.getLayers()) { - if ( - l.feature.properties[cls.options.featureIdField] === - geojson.properties[cls.options.featureIdField] - ) { - cls.removeLayer(l); - cls.addData(geojson); - } - } - }, - false - ); - - source.addEventListener( - "delete", - function deleteEvent(event) { - /* - * On delete events, remove the existing feature based on featureId. The expected data sent by - * this event is a single geojson feature. - */ - let geojson = JSON.parse(event.data); - for (let l of cls.getLayers()) { - if ( - l.feature.properties[cls.options.featureIdField] === - geojson.properties[cls.options.featureIdField] - ) { - cls.removeLayer(l); - } - } - }, - false - ); - - // handle connection open event - source.onopen = function (event) { - /* - * Fired once when readyState changes from 0 (CONNECTING) - * to 1 (CONNECTED). DOES NOT fire when the connection is - * first established, actually fires when the first event - * is received from server. - * - * DO NOT use 'onopen' event to test/confirm - * successful connection to the event server. Instead make a - * request to the event server and have it publish a - * type='message' event. Then use source.onmessage to confirm you - * successfully got the event (this method only works on Firefox). - * Alternatively check source.readyState. - */ - }; - - // handle message event - source.onmessage = function (event) { - /* - * Generic 'message' event handler. - * Can use this to confirm connection to event server - * by making GET request to end point that publishes - * a type='message' event. - */ - //let data = JSON.parse(event.data); - //alert(data.message); - }; - - // handle error event - source.onerror = function (event) { - // reconnect if the connection is closed - if (source.readyState === 2) { - cls.connectToEventStream(); - } - }; + } - this.eventSource = source; + if (typeof featureIdField === "undefined") { + throw Error("Undefined featureIdField option."); } + + // set stream source + const source = new EventSource(this.options.streamUrl); + + source.addEventListener("create", createEvent, false); + source.addEventListener("update", updateEvent, false); + source.addEventListener("delete", deleteEvent, false); + + /** + * handle connection open event + * + * Fired once when readyState changes from 0 (CONNECTING) to 1 (CONNECTED). + * DOES NOT fire when the connection is first established, actually fires + * when the first event is received from server. + * + * DO NOT use 'onopen' event to test/confirm successful connection to the + * event server. Instead make a request to the event server and have it + * publish a type='message' event. Then use source.onmessage to confirm you + * successfully got the event (this method only works on Firefox). + * Alternatively check source.readyState. + */ + source.onopen = function (event) {}; + + /** + * Generic 'message' event handler. + * + * Can use this to confirm connection to event server by making GET request + * to end point that publishes a type='message' event. + */ + source.onmessage = function (event) { + //const data = JSON.parse(event.data); + //alert(data.message); + }; + + /** + * handle error event + */ + source.onerror = function (event) { + // reconnect if the connection is closed + if (source.readyState === 2) { + self.connectToEventStream(); + } + }; + + this.eventSource = source; }, + + /** + * Disconnect from the event server and unsubscribe from all event streams. + */ disconnect: function () { - /* - * Disconnect from the event server and unsubscribe from all event streams. - */ this.eventSource.close(); }, + + /** + * Updates the event stream url option. + * Keyword Arguments: + * newStreamUrl (required) -- The url of the event server stream. + */ setStreamUrl: function (newStreamUrl) { - /* - * Updates the event stream url option. - * Keyword Arguments: - * newStreamUrl (required) -- The url of the event server stream. - */ this.options.streamUrl = newStreamUrl; }, + + /** + * Updates the featureIdField option used to uniquely identify individual + * features. + * + * Keyword Arguments: + * fieldName (required) -- The name of the field used to uniquely identify + * features. + */ setFeatureIdField: function (fieldName) { - /* - * Updates the featureIdField option used to uniquely identify individual features. - * Keyword Arguments: - * fieldName (required) -- The name of the field used to uniquely identify features. - */ this.options.featureIdField = fieldName; }, - switchStream: function (newStream, featureIdField, emptyLayer = false) { - /* - * Disconnect from the current event stream and connect to a new event stream. - * Keyword Arguments: - * newStream (required) -- The url of the event stream. - * featureIdField (required) -- Name of the field used to uniquely identify features. - * emptyLayer (optional) -- Boolean indicating if all features should be removed before switching streams. - * Defaults to false. - */ + /** + * Disconnect from the current event stream and connect to a new event stream. + * + * Keyword Arguments: + * newStream (required) -- The url of the event stream. + * featureIdField (required) -- Name of the field used to uniquely identify + * features. + * emptyLayer (optional) -- Boolean indicating if all features should be + * removed before switching streams. Defaults to false. + */ + switchStream: function (newStream, featureIdField, emptyLayer = false) { if (emptyLayer) { this.clearLayers(); } @@ -162,10 +169,12 @@ var GeoSSE = L.GeoJSON.extend({ // connect to the new stream this.connectToEventStream(); - }, + } }); -// factory function +/** + * factory function + */ L.geoSSE = function (data, options) { return new GeoSSE(data, options); }; From 5e95d582695f11540981309d8cd8bce215e7ed06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 08:11:32 +0200 Subject: [PATCH 03/20] Reuse `deleteEvent` and `createEvent` in `updateEvent`. Fix #3 --- src/Leaflet.GeoSSE.src.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Leaflet.GeoSSE.src.js b/src/Leaflet.GeoSSE.src.js index e9403fa..78448bb 100644 --- a/src/Leaflet.GeoSSE.src.js +++ b/src/Leaflet.GeoSSE.src.js @@ -28,17 +28,8 @@ const GeoSSE = L.GeoJSON.extend({ * expected data sent by this event is a single geojson feature. */ function updateEvent(event) { - const geojson = JSON.parse(event.data); - - for (let l of self.getLayers()) { - if ( - l.feature.properties[featureIdField] === - geojson.properties[featureIdField] - ) { - self.removeLayer(l); - self.addData(geojson); - } - } + deleteEvent(event); + createEvent(event); } /** From 31f71c4d191318170a6f803fdd32b72d893050c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 08:19:41 +0200 Subject: [PATCH 04/20] Optimize looking for layers for deletion --- src/Leaflet.GeoSSE.src.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Leaflet.GeoSSE.src.js b/src/Leaflet.GeoSSE.src.js index 78448bb..4458869 100644 --- a/src/Leaflet.GeoSSE.src.js +++ b/src/Leaflet.GeoSSE.src.js @@ -39,16 +39,17 @@ const GeoSSE = L.GeoJSON.extend({ function deleteEvent(event) { const geojson = JSON.parse(event.data); - for (let l of self.getLayers()) { - if ( - l.feature.properties[featureIdField] === - geojson.properties[featureIdField] - ) { - self.removeLayer(l); - } + const layer = self.getLayers().find(finder, geojson.properties); + + if (layer) { + self.removeLayer(layer); } } + function finder({feature: {properties}}) { + return properties[featureIdField] === this[featureIdField] + } + const self = this; const {featureIdField} = this.options; From 02e6e17b00a5db0b183f69e2bf7b2a37e26938b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 08:34:01 +0200 Subject: [PATCH 05/20] Add support for `FeatureCollection`s. Fix #2 --- src/Leaflet.GeoSSE.src.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Leaflet.GeoSSE.src.js b/src/Leaflet.GeoSSE.src.js index 4458869..a1102ed 100644 --- a/src/Leaflet.GeoSSE.src.js +++ b/src/Leaflet.GeoSSE.src.js @@ -13,6 +13,10 @@ const GeoSSE = L.GeoJSON.extend({ * stream. */ connectToEventStream: function () { + function addFeature(feature) { + self.addData(feature); + } + /** * On create events, simply add the data. The expected data sent this event * is a geojson feature. @@ -20,7 +24,11 @@ const GeoSSE = L.GeoJSON.extend({ function createEvent(event) { const geojson = JSON.parse(event.data); - self.addData(geojson); + if(geojson.type === "Feature") { + return addFeature(geojson); + } + + geojson.features.forEach(addFeature); } /** @@ -39,17 +47,25 @@ const GeoSSE = L.GeoJSON.extend({ function deleteEvent(event) { const geojson = JSON.parse(event.data); - const layer = self.getLayers().find(finder, geojson.properties); - - if (layer) { - self.removeLayer(layer); + if(geojson.type === "Feature") { + return removeFeature(geojson); } + + geojson.features.forEach(removeFeature); } function finder({feature: {properties}}) { return properties[featureIdField] === this[featureIdField] } + function removeFeature(feature) { + const layer = self.getLayers().find(finder, feature.properties); + + if (layer) { + self.removeLayer(layer); + } + } + const self = this; const {featureIdField} = this.options; From 427dffb88bf75415e44218389a984af8386eddbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 08:34:47 +0200 Subject: [PATCH 06/20] Sorted event handlers --- src/Leaflet.GeoSSE.src.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Leaflet.GeoSSE.src.js b/src/Leaflet.GeoSSE.src.js index a1102ed..8acf7d2 100644 --- a/src/Leaflet.GeoSSE.src.js +++ b/src/Leaflet.GeoSSE.src.js @@ -31,15 +31,6 @@ const GeoSSE = L.GeoJSON.extend({ geojson.features.forEach(addFeature); } - /** - * On update events, replace the existing feature based on featureId. The - * expected data sent by this event is a single geojson feature. - */ - function updateEvent(event) { - deleteEvent(event); - createEvent(event); - } - /** * On delete events, remove the existing feature based on featureId. The * expected data sent by this event is a single geojson feature. @@ -66,6 +57,16 @@ const GeoSSE = L.GeoJSON.extend({ } } + /** + * On update events, replace the existing feature based on featureId. The + * expected data sent by this event is a single geojson feature. + */ + function updateEvent(event) { + deleteEvent(event); + createEvent(event); + } + + const self = this; const {featureIdField} = this.options; @@ -83,8 +84,8 @@ const GeoSSE = L.GeoJSON.extend({ const source = new EventSource(this.options.streamUrl); source.addEventListener("create", createEvent, false); - source.addEventListener("update", updateEvent, false); source.addEventListener("delete", deleteEvent, false); + source.addEventListener("update", updateEvent, false); /** * handle connection open event From a428e690e6e4b793c5908b590ca8e88d93d994b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 08:38:12 +0200 Subject: [PATCH 07/20] Updated minified version --- dist/Leaflet.GeoSSE.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/Leaflet.GeoSSE.min.js b/dist/Leaflet.GeoSSE.min.js index d633c3e..9a70e2c 100644 --- a/dist/Leaflet.GeoSSE.min.js +++ b/dist/Leaflet.GeoSSE.min.js @@ -1 +1 @@ -"use strict";var GeoSSE=L.GeoJSON.extend({connectToEventStream:function(){let cls=this;if(typeof this.options.streamUrl==="undefined"){throw Error("Undefined event streamUrl.")}else if(typeof this.options.featureIdField==="undefined"){throw Error("Undefined featureIdField option.")}else{let source=new EventSource(this.options.streamUrl);source.addEventListener("create",function createEvent(event){let geojson=JSON.parse(event.data);cls.addData(geojson)},false);source.addEventListener("update",function updateEvent(event){let geojson=JSON.parse(event.data);for(let l of cls.getLayers()){if(l.feature.properties[cls.options.featureIdField]===geojson.properties[cls.options.featureIdField]){cls.removeLayer(l);cls.addData(geojson)}}},false);source.addEventListener("delete",function deleteEvent(event){let geojson=JSON.parse(event.data);for(let l of cls.getLayers()){if(l.feature.properties[cls.options.featureIdField]===geojson.properties[cls.options.featureIdField]){cls.removeLayer(l)}}},false);source.onopen=function(event){};source.onmessage=function(event){};source.onerror=function(event){if(source.readyState===2){cls.connectToEventStream()}};this.eventSource=source}},disconnect:function(){this.eventSource.close()},setStreamUrl:function(newStreamUrl){this.options.streamUrl=newStreamUrl},setFeatureIdField:function(fieldName){this.options.featureIdField=fieldName},switchStream:function(newStream,featureIdField,emptyLayer=false){if(emptyLayer){this.clearLayers()}this.setStreamUrl(newStream);this.setFeatureIdField(featureIdField);this.disconnect();this.connectToEventStream()}});L.geoSSE=function(data,options){return new GeoSSE(data,options)};export default GeoSSE; \ No newline at end of file +"use strict";const GeoSSE=L.GeoJSON.extend({connectToEventStream:function(){function addFeature(feature){self.addData(feature)}function createEvent(event){const geojson=JSON.parse(event.data);if(geojson.type==="Feature"){return addFeature(geojson)}geojson.features.forEach(addFeature)}function deleteEvent(event){const geojson=JSON.parse(event.data);if(geojson.type==="Feature"){return removeFeature(geojson)}geojson.features.forEach(removeFeature)}function finder({feature:{properties:properties}}){return properties[featureIdField]===this[featureIdField]}function removeFeature(feature){const layer=self.getLayers().find(finder,feature.properties);if(layer){self.removeLayer(layer)}}function updateEvent(event){deleteEvent(event);createEvent(event)}const self=this;const{featureIdField:featureIdField}=this.options;if(typeof this.options.streamUrl==="undefined"){throw Error("Undefined event streamUrl.")}if(typeof featureIdField==="undefined"){throw Error("Undefined featureIdField option.")}const source=new EventSource(this.options.streamUrl);source.addEventListener("create",createEvent,false);source.addEventListener("delete",deleteEvent,false);source.addEventListener("update",updateEvent,false);source.onopen=function(event){};source.onmessage=function(event){};source.onerror=function(event){if(source.readyState===2){self.connectToEventStream()}};this.eventSource=source},disconnect:function(){this.eventSource.close()},setStreamUrl:function(newStreamUrl){this.options.streamUrl=newStreamUrl},setFeatureIdField:function(fieldName){this.options.featureIdField=fieldName},switchStream:function(newStream,featureIdField,emptyLayer=false){if(emptyLayer){this.clearLayers()}this.setStreamUrl(newStream);this.setFeatureIdField(featureIdField);this.disconnect();this.connectToEventStream()}});L.geoSSE=function(data,options){return new GeoSSE(data,options)};export default GeoSSE; \ No newline at end of file From c04c9924515f0378749c86f58d579c40761b8369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Legan=C3=A9s-Combarro=20=27piranna?= Date: Fri, 5 Apr 2024 08:58:48 +0200 Subject: [PATCH 08/20] Remove `featureIdField`, use GeoJSON spec `feature.id` field --- README.md | 6 ++---- dist/Leaflet.GeoSSE.min.js | 2 +- examples/index.html | 6 +++--- src/Leaflet.GeoSSE.src.js | 41 ++++++++++++++++---------------------- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index ddd6ac0..0857f5e 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ The geojson feature's properties must include a field that uniquely identifies t "type": "Point", "coordinates": [125.6, 10.1] }, + "id": 1, "properties": { - "featureId": 1, "name": "My Feature" } } @@ -40,14 +40,13 @@ Add the file to your map HTML head tag below LeafletJS. ### Initializing -Initialize same as any `L.geoJson` instance. You must pass in a `streamUrl` and `featureIdField` option to identify the event source and individual features respectively. +Initialize same as any `L.geoJson` instance. You must pass in a `streamUrl` to identify the event source. Initialize an empty layer when you don't care about history and only want to monitor events that are created after establishing connection to event stream. ```js var sseLyr = L.geoSSE(null, { streamUrl: "https://my-site.com/stream", - featureIdField: "featureId", // set other layer options... }); ``` @@ -57,7 +56,6 @@ Alternatively you can initialize with some existing data when you want to establ ```js var sseLyr = L.geoSSE('my-data.geojson', { streamUrl: 'https://my-site.com/stream' - featureIdField: 'featureId' // set other layer options... }); ``` diff --git a/dist/Leaflet.GeoSSE.min.js b/dist/Leaflet.GeoSSE.min.js index 9a70e2c..f7426d1 100644 --- a/dist/Leaflet.GeoSSE.min.js +++ b/dist/Leaflet.GeoSSE.min.js @@ -1 +1 @@ -"use strict";const GeoSSE=L.GeoJSON.extend({connectToEventStream:function(){function addFeature(feature){self.addData(feature)}function createEvent(event){const geojson=JSON.parse(event.data);if(geojson.type==="Feature"){return addFeature(geojson)}geojson.features.forEach(addFeature)}function deleteEvent(event){const geojson=JSON.parse(event.data);if(geojson.type==="Feature"){return removeFeature(geojson)}geojson.features.forEach(removeFeature)}function finder({feature:{properties:properties}}){return properties[featureIdField]===this[featureIdField]}function removeFeature(feature){const layer=self.getLayers().find(finder,feature.properties);if(layer){self.removeLayer(layer)}}function updateEvent(event){deleteEvent(event);createEvent(event)}const self=this;const{featureIdField:featureIdField}=this.options;if(typeof this.options.streamUrl==="undefined"){throw Error("Undefined event streamUrl.")}if(typeof featureIdField==="undefined"){throw Error("Undefined featureIdField option.")}const source=new EventSource(this.options.streamUrl);source.addEventListener("create",createEvent,false);source.addEventListener("delete",deleteEvent,false);source.addEventListener("update",updateEvent,false);source.onopen=function(event){};source.onmessage=function(event){};source.onerror=function(event){if(source.readyState===2){self.connectToEventStream()}};this.eventSource=source},disconnect:function(){this.eventSource.close()},setStreamUrl:function(newStreamUrl){this.options.streamUrl=newStreamUrl},setFeatureIdField:function(fieldName){this.options.featureIdField=fieldName},switchStream:function(newStream,featureIdField,emptyLayer=false){if(emptyLayer){this.clearLayers()}this.setStreamUrl(newStream);this.setFeatureIdField(featureIdField);this.disconnect();this.connectToEventStream()}});L.geoSSE=function(data,options){return new GeoSSE(data,options)};export default GeoSSE; \ No newline at end of file +"use strict";const GeoSSE=L.GeoJSON.extend({connectToEventStream:function(){function addFeature(feature){if(!feature.id){return console.warn("Feature id is required to add a feature, so it can be removed.",feature)}self.addData(feature)}function createEvent(event){const geojson=JSON.parse(event.data);if(geojson.type==="Feature"){return addFeature(geojson)}geojson.features.forEach(addFeature)}function deleteEvent(event){const geojson=JSON.parse(event.data);if(geojson.type==="Feature"){return removeFeature(geojson)}geojson.features.forEach(removeFeature)}function finder({feature:{id:id}}){return id===this.id}function removeFeature(feature){if(!feature.id){return console.warn("Feature id is required to delete a feature.",feature)}const layer=self.getLayers().find(finder,feature);if(layer){self.removeLayer(layer)}}function updateEvent(event){deleteEvent(event);createEvent(event)}const self=this;if(typeof this.options.streamUrl==="undefined"){throw Error("Undefined event streamUrl.")}const source=new EventSource(this.options.streamUrl);source.addEventListener("create",createEvent,false);source.addEventListener("delete",deleteEvent,false);source.addEventListener("update",updateEvent,false);source.onopen=function(event){};source.onmessage=function(event){};source.onerror=function(event){if(source.readyState===2){self.connectToEventStream()}};this.eventSource=source},disconnect:function(){this.eventSource.close()},setStreamUrl:function(newStreamUrl){this.options.streamUrl=newStreamUrl},switchStream:function(newStream,emptyLayer=false){if(emptyLayer){this.clearLayers()}this.setStreamUrl(newStream);this.disconnect();this.connectToEventStream()}});L.geoSSE=function(data,options){return new GeoSSE(data,options)};export default GeoSSE; \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 6169910..2715549 100644 --- a/examples/index.html +++ b/examples/index.html @@ -18,14 +18,14 @@