diff --git a/dist/index.html b/dist/index.html
index 8690013..455e7b6 100644
--- a/dist/index.html
+++ b/dist/index.html
@@ -201,11 +201,12 @@
Select/highlight tree
const parameters = getParameters();
map = new greenstand.Map({
onLoad: () => console.log("onload"),
- onClickTree: () => console.log("onClickTree"),
onFindNearestAt: () => console.log("onFindNearstAt"),
onError: () => console.log("onError"),
});
map.on(greenstand.Map.REGISTERED_EVENTS.MOVE_END, handleMoveEnd);
+ map.on(greenstand.Map.REGISTERED_EVENTS.TREE_SELECTED, (data) => console.log(data));
+ map.on(greenstand.Map.REGISTERED_EVENTS.MULTIPLE_TREES_SELECTED, (data) => console.log(data));
map.mount(document.getElementById("map"));
map.setFilters(parameters);
};
diff --git a/package-lock.json b/package-lock.json
index 24bdddb..7c95d3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "treetracker-web-map-core",
- "version": "2.6.0",
+ "version": "2.7.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "treetracker-web-map-core",
- "version": "2.6.0",
+ "version": "2.7.2",
"license": "ISC",
"dependencies": {
"axios": "^0.24.0",
@@ -14,6 +14,7 @@
"events": "^3.3.0",
"expect-runtime": "^0.10.1",
"leaflet": "^1.7.1",
+ "leaflet-draw": "^1.0.4",
"leaflet-utfgrid": "git+https://github.com/dadiorchen/Leaflet.UTFGrid.git",
"leaflet.gridlayer.googlemutant": "^0.12.1",
"lodash": "^4.17.21",
@@ -9137,6 +9138,11 @@
"resolved": "https://registry.npm.taobao.org/leaflet/download/leaflet-1.7.1.tgz",
"integrity": "sha1-ENaEkW7f4b9B1oijuXEnwDIqKhk="
},
+ "node_modules/leaflet-draw": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/leaflet-draw/-/leaflet-draw-1.0.4.tgz",
+ "integrity": "sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ=="
+ },
"node_modules/leaflet-utfgrid": {
"version": "0.3.0",
"resolved": "git+ssh://git@github.com/dadiorchen/Leaflet.UTFGrid.git#2bdd74c2ca5298bcc820bc9e0deeb1ae69556526",
@@ -20332,6 +20338,11 @@
"resolved": "https://registry.npm.taobao.org/leaflet/download/leaflet-1.7.1.tgz",
"integrity": "sha1-ENaEkW7f4b9B1oijuXEnwDIqKhk="
},
+ "leaflet-draw": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/leaflet-draw/-/leaflet-draw-1.0.4.tgz",
+ "integrity": "sha512-rsQ6saQO5ST5Aj6XRFylr5zvarWgzWnrg46zQ1MEOEIHsppdC/8hnN8qMoFvACsPvTioAuysya/TVtog15tyAQ=="
+ },
"leaflet-utfgrid": {
"version": "git+ssh://git@github.com/dadiorchen/Leaflet.UTFGrid.git#2bdd74c2ca5298bcc820bc9e0deeb1ae69556526",
"from": "leaflet-utfgrid@git+https://github.com/dadiorchen/Leaflet.UTFGrid.git",
diff --git a/package.json b/package.json
index f826e61..3540c2d 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"events": "^3.3.0",
"expect-runtime": "^0.10.1",
"leaflet": "^1.7.1",
+ "leaflet-draw": "^1.0.4",
"leaflet-utfgrid": "git+https://github.com/dadiorchen/Leaflet.UTFGrid.git",
"leaflet.gridlayer.googlemutant": "^0.12.1",
"lodash": "^4.17.21",
diff --git a/src/Alert.js b/src/Alert.js
index c79cdb6..d5f1e0f 100644
--- a/src/Alert.js
+++ b/src/Alert.js
@@ -2,7 +2,9 @@
import './style.css'
export default class Alert {
- constructor() {}
+ constructor() {
+ this.id = 0
+ }
mount(element) {
// create a div and mount to the element
@@ -26,11 +28,15 @@ export default class Alert {
element.appendChild(this.alert)
}
- show(message) {
+ show(message, time) {
+ clearTimeout(this.id)
document.getElementsByClassName(
'greenstand-alert-message-box',
)[0].innerHTML = message
this.alert.style.display = 'block'
+ if (time) {
+ this.id = setTimeout(() => this.hide(), time)
+ }
}
hide() {
diff --git a/src/DrawTool.js b/src/DrawTool.js
new file mode 100644
index 0000000..14ec08d
--- /dev/null
+++ b/src/DrawTool.js
@@ -0,0 +1,88 @@
+import 'leaflet-draw'
+import 'leaflet-draw/dist/leaflet.draw.css'
+
+class DrawTool {
+ constructor(map) {
+ this.map = map
+ this.isDrawingMode = false
+ this._loadEditor()
+ this.onSelecetMultTree = null
+ }
+ onSelecetMultiplePoints(funct) {
+ this.onSelecetMultPoints = funct
+ }
+ async _loadEditor() {
+ // FeatureGroup is to store editable layers
+ var drawnItems = new window.L.FeatureGroup()
+ this.map.addLayer(drawnItems)
+ var drawControl = new window.L.Control.Draw({
+ draw: {
+ marker: false,
+ polyline: false,
+ circlemarker: false,
+ circle: false,
+ polygon: {
+ allowIntersection: false,
+ },
+ },
+ edit: {
+ featureGroup: drawnItems,
+ poly: {
+ allowIntersection: false,
+ },
+ },
+ })
+ var editOnlyControl = new window.L.Control.Draw({
+ draw: false,
+ edit: {
+ featureGroup: drawnItems,
+ poly: {
+ allowIntersection: false,
+ },
+ },
+ })
+ this.map.addControl(drawControl)
+
+ this.map.on('draw:created', async (e) => {
+ let layer = e.layer
+ let points = layer._latlngs[0]
+ drawnItems.addLayer(layer)
+ //disable draw tool
+ this.map.removeControl(drawControl)
+ this.map.addControl(editOnlyControl)
+ if (this.onSelecetMultPoints) {
+ this.onSelecetMultPoints(points)
+ }
+ })
+ this.map.on('draw:edited', async (e) => {
+ let layers = e.layers._layers
+ let polygon = Object.values(layers)[0]
+ if (polygon) {
+ if (this.onSelecetMultPoints) {
+ this.onSelecetMultPoints(polygon._latlngs[0])
+ }
+ }
+ })
+ this.map.on('draw:deleted', async (e) => {
+ //enable draw tool if all polygons deleted
+ if (drawnItems.getLayers().length == 0) {
+ this.map.removeControl(editOnlyControl)
+ this.map.addControl(drawControl)
+ }
+ })
+ //enable drawing mode which prevent user move when clicking on icon tree or group
+ this.map.on('draw:drawstart ', (e) => {
+ this.isDrawingMode = true
+ })
+ this.map.on('draw:drawstop ', (e) => {
+ this.isDrawingMode = false
+ })
+ this.map.on('draw:editstart ', (e) => {
+ this.isDrawingMode = true
+ })
+ this.map.on('draw:editstop ', (e) => {
+ this.isDrawingMode = false
+ })
+ }
+}
+export default DrawTool
diff --git a/src/Map.js b/src/Map.js
index b600e5e..ef7fa05 100644
--- a/src/Map.js
+++ b/src/Map.js
@@ -7,7 +7,9 @@ import expect from 'expect-runtime'
import log from 'loglevel'
import _ from 'lodash'
import 'leaflet'
+import 'leaflet-draw'
import 'leaflet/dist/leaflet.css'
+import 'leaflet-draw/dist/leaflet.draw.css'
import 'leaflet-utfgrid/L.UTFGrid'
import 'leaflet.gridlayer.googlemutant'
@@ -22,6 +24,7 @@ import Alert from './Alert'
import TileLoadingMonitor from './TileLoadingMonitor'
import ButtonPanel from './ButtonPanel'
import NearestTreeArrows from './NearestTreeArrows'
+import DrawTool from './DrawTool'
class MapError extends Error {}
@@ -31,8 +34,14 @@ export default class Map {
// events
static REGISTERED_EVENTS = {
TREE_SELECTED: 'tree-selected',
+ MULTIPLE_TREES_SELECTED: 'multiple-trees-selected',
TREE_UNSELECTED: 'tree-unselected',
MOVE_END: 'move-end',
+ LOAD: 'load',
+ TREE_CLICKED: 'tree-clicked',
+ //not implemented this event yet
+ // FIND_NEAREST: 'find-nearest',
+ ERROR: 'error',
}
constructor(options) {
@@ -70,6 +79,19 @@ export default class Map {
this._mountDomElement = null
log.warn('map core version:', require('../package.json').version)
+
+ // Deprecation warnings
+ let deprecatedMethods = [
+ 'onLoad',
+ 'onClickTree',
+ 'onFindNearestAt',
+ 'onError',
+ ]
+ deprecatedMethods.forEach((method) => {
+ if (this[method]) {
+ log.warn(`${method} is deprecated. Use map.on() instead.`)
+ }
+ })
}
/** *************************** static *************************** */
@@ -306,7 +328,7 @@ export default class Map {
)
this.layerUtfGrid.on('click', (e) => {
log.warn('click:', e)
- if (e.data) {
+ if (e.data && !this.drawTool.isDrawingMode) {
this._clickMarker(Map._parseUtfData(e.data))
}
})
@@ -518,6 +540,9 @@ export default class Map {
if (this.onClickTree) {
this.onClickTree(data)
}
+ if (this.events.listenerCount(Map.REGISTERED_EVENTS.TREE_CLICKED) > 0) {
+ this.events.emit(Map.REGISTERED_EVENTS.TREE_CLICKED, data)
+ }
} else if (data.type === 'cluster') {
if (data.zoom_to) {
log.info('found zoom to:', data.zoom_to)
@@ -970,6 +995,37 @@ export default class Map {
: this.nearestTreeArrow.showArrow(placement)
}
+ async _getTreesFromPoly(poly) {
+ try {
+ let polypoints = poly.map(({ lat, lng }) => {
+ return {
+ lat,
+ lon: lng,
+ }
+ })
+ //add another first point to make polygon enclosed
+ polypoints.push({ lat: poly[0].lat, lon: poly[0].lng })
+ this.alert.show('Collecting trees data')
+ const result = await this.requester.request({
+ url: `${this.queryApiServerUrl}/gis`,
+ data: {
+ polygon: polypoints,
+ },
+ headers: { 'Content-Type': 'application/json' },
+ method: 'post',
+ })
+ this.alert.hide()
+ return result
+ } catch (err) {
+ this.alert.show(
+ 'Can not collecting tree data, please try again later',
+ 5000,
+ )
+ console.info(err.message || 'Unknown')
+ return null
+ }
+ }
+
async _moveToNearestTree() {
const nearest = await this._getNearest()
if (nearest) {
@@ -1307,7 +1363,9 @@ export default class Map {
if (this.onLoad) {
this.onLoad()
}
-
+ if (this.events.listenerCount(Map.REGISTERED_EVENTS.LOAD) > 0) {
+ this.events.emit(Map.REGISTERED_EVENTS.LOAD)
+ }
if (this.debug) {
await this._loadDebugLayer()
}
@@ -1318,15 +1376,61 @@ export default class Map {
if (this.onError) {
this.onError(e)
}
+ if (this.events.listenerCount(Map.REGISTERED_EVENTS.ERROR) > 0) {
+ this.events.emit(Map.REGISTERED_EVENTS.ERROR, e)
+ }
}
}
}
on(eventName, handler) {
+ const isValidEvent = Object.values(Map.REGISTERED_EVENTS).includes(
+ eventName,
+ )
+ if (!isValidEvent) {
+ log.error('Invalid event name:', eventName)
+ return
+ }
//TODO check event name enum
if (handler) {
log.info('register event:', eventName)
this.events.on(eventName, handler)
+ } else {
+ log.error('No handler provided for event:', eventName)
+ }
+ }
+
+ off(eventName, handler) {
+ const isValidEvent = Object.values(Map.REGISTERED_EVENTS).includes(
+ eventName,
+ )
+ if (!isValidEvent) {
+ log.error('Invalid event name:', eventName)
+ return
+ }
+
+ if (handler) {
+ log.info('remove event:', eventName)
+ this.events.off(eventName, handler)
+ } else {
+ log.error('No handler provided for event removal:', eventName)
+ }
+ }
+
+ once(eventName, handler) {
+ const isValidEvent = Object.values(Map.REGISTERED_EVENTS).includes(
+ eventName,
+ )
+ if (!isValidEvent) {
+ log.error('Invalid event name:', eventName)
+ return
+ }
+
+ if (handler) {
+ log.info('register one-time event:', eventName)
+ this.events.once(eventName, handler)
+ } else {
+ log.error('No handler provided for this one-time event:', eventName)
}
}
@@ -1349,7 +1453,46 @@ export default class Map {
await this._unselectMarker()
await this._unloadTileServer()
await this._loadTileServer()
+ await this._loadEditor()
+ }
+ }
+
+ async _loadEditor() {
+ //create draw tool
+ this.drawTool = new DrawTool(this.map)
+
+ //add panel to track how many selected
+ var panel = window.L.control({ position: 'topright' })
+ panel.onAdd = function (map) {
+ var div = window.L.DomUtil.create('div', 'info')
+ div.innerHTML = `
+
+
Tree Count: 0
+ `
+ return div
+ }
+ panel.addTo(this.map)
+
+ //create event for select multiple trees event
+ const onSelectMultTrees = async (points) => {
+ const result = await this._getTreesFromPoly(points)
+ var total = result?.trees?.length || 0
+ document.getElementById('treeTotal').innerHTML = total
+ if (
+ this.events.listenerCount(
+ Map.REGISTERED_EVENTS.MULTIPLE_TREES_SELECTED,
+ ) > 0
+ ) {
+ this.events.emit(
+ Map.REGISTERED_EVENTS.MULTIPLE_TREES_SELECTED,
+ result?.trees || [],
+ )
+ }
}
+ //add select multiple trees event to the draw tool
+ this.drawTool.onSelecetMultiplePoints(onSelectMultTrees)
}
clearSelection() {
diff --git a/src/Map.test.mjs b/src/Map.test.mjs
index 532cc5e..bfc9791 100644
--- a/src/Map.test.mjs
+++ b/src/Map.test.mjs
@@ -45,4 +45,28 @@ describe('Map', () => {
zoomLevel: 3,
})
})
+
+ // it("Test trigger events",async()=>{
+ // const request = jest.fn(() => response)
+ // Requester.mockImplementation(() => ({
+ // request,
+ // }))
+ // const event_check = {
+ // tree_selected:0,
+ // }
+ // const map = new Map({
+ // userid: '1',
+ // width: 1440,
+ // height: 510,
+ // moreEffect: false,
+ // filters: {
+ // wallet: 'mayeda',
+ // },
+ // //function to test error event
+ // _mountComponents: ()=>{
+ // throw new Error("Error event");
+ // }
+ // })
+ // map.on(map.REGISTERED_EVENTS.TREE_SELECTED)
+ // })
})