maplibre-gl-js-amplify
Version:
MapLibre Plugin to Support Amplify Geo Integration
332 lines (331 loc) • 15.6 kB
JavaScript
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);
}
}