UNPKG

maplibre-gl-js-amplify

Version:

MapLibre Plugin to Support Amplify Geo Integration

332 lines (331 loc) 15.6 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import maplibregl from 'maplibre-gl'; import { Geo } from '@aws-amplify/geo'; import { drawGeofences } from '../drawGeofences'; import { isValidGeofenceId, getGeofenceFeatureFromPolygon, getGeofenceFeatureArray, isExistingGeofenceId, getDistanceBetweenCoordinates, } from '../geofenceUtils'; import { GEOFENCE_COLOR, GEOFENCE_BORDER_COLOR } from '../constants'; import { AmplifyGeofenceControlUI } from './ui'; import { AmplifyMapDraw } from './AmplifyMapDraw'; import { createElement } from '../utils'; const FIT_BOUNDS_PADDING = { left: 240 }; // Default to 240px right now because of the left nav export class AmplifyGeofenceControl { constructor(options) { this._geofenceCollectionId = options === null || options === void 0 ? void 0 : options.geofenceCollectionId; this._loadedGeofences = {}; this._displayedGeofences = []; this.changeMode = this.changeMode.bind(this); this.loadInitialGeofences = this.loadInitialGeofences.bind(this); this.loadMoreGeofences = this.loadMoreGeofences.bind(this); this._loadGeofence = this._loadGeofence.bind(this); this.updateInputRadius = this.updateInputRadius.bind(this); this.saveGeofence = this.saveGeofence.bind(this); this.editGeofence = this.editGeofence.bind(this); this.deleteGeofence = this.deleteGeofence.bind(this); this.displayAllGeofences = this.displayAllGeofences.bind(this); this.hideAllGeofences = this.hideAllGeofences.bind(this); this.addEditableGeofence = this.addEditableGeofence.bind(this); this.setEditingModeEnabled = this.setEditingModeEnabled.bind(this); this.displayHighlightedGeofence = this.displayHighlightedGeofence.bind(this); this.hideHighlightedGeofence = this.hideHighlightedGeofence.bind(this); this.displayGeofence = this.displayGeofence.bind(this); this.hideGeofence = this.hideGeofence.bind(this); this.fitGeofence = this.fitGeofence.bind(this); this.fitAllGeofences = this.fitAllGeofences.bind(this); } /********************************************************************** Public Methods for AmplifyGeofenceControl **********************************************************************/ getDefaultPosition() { return 'full-screen'; } onRemove() { this._ui.removeElement(this._container); } // Reorders MapLibre canvas class names to fix a mapbox draw bug - https://github.com/mapbox/mapbox-gl-draw/pull/1079 reorderMapLibreClassNames() { const mapCanvas = document .getElementsByClassName('maplibregl-canvas') .item(0); if (mapCanvas) { mapCanvas.className = 'mapboxgl-canvas maplibregl-canvas'; } } onAdd(map) { this._map = map; this.reorderMapLibreClassNames(); this._container = createElement('div', 'geofence-ctrl maplibregl-ctrl'); this._ui = AmplifyGeofenceControlUI(this, this._container); this._amplifyDraw = new AmplifyMapDraw(map, this._ui); this._ui.registerControlPosition(map, 'full-screen'); this._ui.createGeofenceListContainer(); // Draw the geofences source to the map so we can update it on geofences load/creation this._map.once('load', function () { // Prevents warnings on multiple re-renders, especially when rendered in react if (this._map.getSource('displayedGeofences')) { return; } this._drawGeofencesOutput = drawGeofences('displayedGeofences', [], this._map, { fillColor: GEOFENCE_COLOR, borderColor: GEOFENCE_BORDER_COLOR, borderOpacity: 1, }); this._highlightedGeofenceOutput = drawGeofences('highlightedGeofence', [], this._map, { fillColor: GEOFENCE_COLOR, borderColor: GEOFENCE_BORDER_COLOR, borderOpacity: 1, borderWidth: 6, }); this.loadInitialGeofences(); map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'bottom-right'); }.bind(this)); this._map.on('draw.update', () => { const coordinates = this._amplifyDraw._mapBoxDraw.getAll().features[0].geometry.coordinates[0]; const radius = getDistanceBetweenCoordinates(coordinates[0], coordinates[Math.floor(coordinates.length / 2)]) / 2; this._ui.updateGeofenceRadius(radius.toFixed(2)); }); return this._container; } createGeofence(geofenceId) { return __awaiter(this, void 0, void 0, function* () { if (!geofenceId || geofenceId.length === 0) { this._ui.createAddGeofencePromptError('Geofence ID is empty.'); return; } if (!isValidGeofenceId(geofenceId)) { this._ui.createAddGeofencePromptError('Geofence ID contains special characters.'); return; } if (isExistingGeofenceId(geofenceId, this._loadedGeofences)) { this._ui.createAddGeofencePromptError('Geofence ID already exists.'); return; } return this.saveGeofence(geofenceId); }); } saveGeofence(geofenceId) { return __awaiter(this, void 0, void 0, function* () { const feature = this._amplifyDraw.get(this._editingGeofenceId); const idToSave = geofenceId || this._editingGeofenceId; const response = yield Geo.saveGeofences({ geofenceId: idToSave, geometry: { polygon: feature.geometry['coordinates'] }, }); if (response.errors[0]) { const err = response.errors[0]; throw new Error(`There was an error saving geofence with id ${idToSave}: ${err.error.code} - ${err.error.message}`); } const success = response.successes[0]; const savedGeofence = { geofenceId: success.geofenceId, geometry: { polygon: feature.geometry['coordinates'] }, }; // render geofence to the map and add it to the list this._loadGeofence(savedGeofence); this.displayGeofence(savedGeofence.geofenceId); this.setEditingModeEnabled(false); return savedGeofence.geofenceId; }); } // Each page loads 100 geofences loadInitialGeofences() { return __awaiter(this, void 0, void 0, function* () { try { const { entries, nextToken } = yield Geo.listGeofences(); this._listGeofencesNextToken = nextToken; const loadGeofence = this._loadGeofence; entries.forEach((geofence) => loadGeofence(geofence)); this._ui.updateGeofenceCount(Object.keys(this._loadedGeofences).length); } catch (e) { throw new Error(`Error calling listGeofences: ${e}`); } }); } loadMoreGeofences() { return __awaiter(this, void 0, void 0, function* () { if (this._listGeofencesNextToken) { try { const { entries, nextToken } = yield Geo.listGeofences({ nextToken: this._listGeofencesNextToken, }); this._listGeofencesNextToken = nextToken; const loadGeofence = this._loadGeofence; entries.forEach((geofence) => loadGeofence(geofence)); this._ui.updateGeofenceCount(Object.keys(this._loadedGeofences).length); } catch (e) { throw new Error(`Error calling listGeofences: ${e}`); } } }); } editGeofence(geofenceId) { this.setEditingModeEnabled(true); const geofence = this._loadedGeofences[geofenceId]; if (!geofence) { throw new Error(`Geofence with id ${geofenceId} does not exist`); } // render in mapboxdraw const feature = getGeofenceFeatureFromPolygon(geofence.geometry.polygon); const data = Object.assign({ id: geofence.geofenceId }, feature); this._amplifyDraw.add(data); this._editingGeofenceId = geofence.geofenceId; } deleteGeofence(geofenceId) { return __awaiter(this, void 0, void 0, function* () { const response = yield Geo.deleteGeofences(geofenceId); if (response.errors[0]) { const err = response.errors[0].error; throw new Error(`There was an error deleting geofence with id ${geofenceId}: ${err.code} - ${err.message}`); } this._ui.removeGeofenceListItem(geofenceId); delete this._loadedGeofences[geofenceId]; this._ui.updateGeofenceCount(Object.keys(this._loadedGeofences).length); this._displayedGeofences = this._displayedGeofences.filter((geofence) => geofence.geofenceId !== geofenceId); this._updateDisplayedGeofences(); return geofenceId; }); } deleteSelectedGeofences() { const idsToDelete = this._displayedGeofences.map((fence) => fence.geofenceId); // FIXME: delete geofence api call here idsToDelete.forEach((id) => { this._ui.removeGeofenceListItem(id); delete this._loadedGeofences[id]; }); this._displayedGeofences = []; this._updateDisplayedGeofences(); } /********************************************************************** Private methods for CRUD Geofences **********************************************************************/ _loadGeofence(geofence) { // If geofence exists remove it from displayed geofences if (this._loadedGeofences[geofence.geofenceId]) { this._displayedGeofences = this._displayedGeofences.filter((fence) => fence.geofenceId !== geofence.geofenceId); } else { // If geofence doesn't exist render a new list item for it this._ui.renderListItem(geofence); } this._loadedGeofences[geofence.geofenceId] = geofence; this._ui.updateGeofenceCount(Object.keys(this._loadedGeofences).length); } displayGeofence(geofenceId) { this._displayedGeofences.push(this._loadedGeofences[geofenceId]); this._updateDisplayedGeofences(); this._ui.updateCheckbox(geofenceId, true); this.fitAllGeofences(); } displayAllGeofences() { this._displayedGeofences.push(...Object.values(this._loadedGeofences)); this._updateDisplayedGeofences(); const checkboxes = document.getElementsByClassName('geofence-ctrl-list-item-checkbox'); Array.from(checkboxes).forEach((checkbox) => (checkbox.checked = this._ui.getCheckboxAllValue())); this.fitAllGeofences(); } fitGeofence(geofenceId) { const mapBounds = this._map.getBounds(); const geofence = this._loadedGeofences[geofenceId]; geofence.geometry.polygon[0].forEach((coord) => { mapBounds.extend(coord); }); this._map.fitBounds(mapBounds, { padding: FIT_BOUNDS_PADDING }); } fitAllGeofences() { let shouldFitBounds = false; const mapBounds = this._map.getBounds(); this._displayedGeofences.forEach((geofence) => { geofence.geometry.polygon[0].forEach((coord) => { if (!mapBounds.contains(coord)) { mapBounds.extend(coord); shouldFitBounds = true; } }); }); if (shouldFitBounds) this._map.fitBounds(mapBounds, { padding: FIT_BOUNDS_PADDING }); } hideGeofence(geofenceId) { this._displayedGeofences = this._displayedGeofences.filter((geofence) => geofence.geofenceId !== geofenceId); this._updateDisplayedGeofences(); this._ui.updateCheckbox(geofenceId, false); } hideAllGeofences() { this._displayedGeofences = []; this._updateDisplayedGeofences(); const checkboxes = document.getElementsByClassName('geofence-ctrl-list-item-checkbox'); Array.from(checkboxes).forEach((checkbox) => (checkbox.checked = this._ui.getCheckboxAllValue())); } _updateDisplayedGeofences() { const feature = getGeofenceFeatureArray(this._displayedGeofences); this._drawGeofencesOutput.setData(feature); } displayHighlightedGeofence(geofenceId) { const geofence = this._loadedGeofences[geofenceId]; if (!geofence) { // eslint-disable-next-line no-console console.warn(`Geofence with id ${geofenceId} does not exist`); return; } const feature = getGeofenceFeatureFromPolygon(geofence.geometry.polygon); this._highlightedGeofenceOutput.setData(feature); this._highlightedGeofenceOutput.show(); } hideHighlightedGeofence() { this._highlightedGeofenceOutput.hide(); } /********************************************************************** Methods for controlling amplify mapbox draw **********************************************************************/ changeMode(mode) { // erase existing mapbox draw content this._amplifyDraw.delete(this._editingGeofenceId); if (mode === 'draw_circle') { this._amplifyDraw.drawCircularGeofence(this._editingGeofenceId); } else { this._amplifyDraw.drawPolygonGeofence(this._editingGeofenceId); } } resetGeofence() { // erase existing mapbox draw content this._amplifyDraw.delete(this._editingGeofenceId); if (isExistingGeofenceId(this._editingGeofenceId, this._loadedGeofences)) { this.editGeofence(this._editingGeofenceId); } else { this._amplifyDraw.drawPolygonGeofence(this._editingGeofenceId); } } // Disables add button and selecting items from geofence list setEditingModeEnabled(enabled) { enabled ? this._amplifyDraw.enable() : this._amplifyDraw.disable(); enabled ? this._drawGeofencesOutput.hide() : this._drawGeofencesOutput.show(); this._ui.setGeofenceListEnabled(!enabled); } updateInputRadius(event) { const radiusString = event.target.value; const radius = parseInt(radiusString); if (isNaN(radius)) { return; } this._amplifyDraw.drawCircularGeofence(this._editingGeofenceId, radius); } addEditableGeofence() { this._editingGeofenceId = 'tempGeofence'; this._amplifyDraw.drawCircularGeofence('tempGeofence'); this.setEditingModeEnabled(true); } }