From f729dac8c8d423d0c60b061433492c67e5673494 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Mandado=20Almajano?=
<50873010+atomicsulfate@users.noreply.github.com>
Date: Fri, 4 Dec 2020 10:00:09 +0100
Subject: [PATCH] MINOR: Add dynamic markers example. (#1997)
* MINOR: Add dynamic markers example.
* MINOR: Rename example.
---
@here/harp-examples/src/markers_dynamic.ts | 192 +++++++++++++++++++++
1 file changed, 192 insertions(+)
create mode 100644 @here/harp-examples/src/markers_dynamic.ts
diff --git a/@here/harp-examples/src/markers_dynamic.ts b/@here/harp-examples/src/markers_dynamic.ts
new file mode 100644
index 0000000000..6d111c430f
--- /dev/null
+++ b/@here/harp-examples/src/markers_dynamic.ts
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017-2020 HERE Europe B.V.
+ * Licensed under Apache 2.0, see full license in LICENSE
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { FeaturesDataSource, MapViewPointFeature } from "@here/harp-features-datasource";
+import { GeoCoordinates } from "@here/harp-geoutils";
+import { MapControls, MapControlsUI } from "@here/harp-map-controls";
+import { CopyrightElementHandler, MapView } from "@here/harp-mapview";
+import {
+ APIFormat,
+ AuthenticationMethod,
+ GeoJsonDataProvider,
+ VectorTileDataSource
+} from "@here/harp-vectortile-datasource";
+
+import { apikey, copyrightInfo } from "../config";
+
+/**
+ * This examples shows how to render dynamically generated GeoJSON points as markers with picking
+ * support.
+ */
+export namespace DynamicMarkersExample {
+ const icons = [
+ {
+ name: "redIcon",
+ url:
+ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOCIgaGVpZ2h0PSI0NyIgdmlld0JveD0iMCAwIDM4IDQ3Ij48ZyBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjMEYxNjIxIiBmaWxsLW9wYWNpdHk9Ii40IiBkPSJNMTUgNDZjMCAuMzE3IDEuNzkuNTc0IDQgLjU3NHM0LS4yNTcgNC0uNTc0YzAtLjMxNy0xLjc5LS41NzQtNC0uNTc0cy00IC4yNTctNCAuNTc0eiI+PC9wYXRoPjxwYXRoIGZpbGw9IiNiNjAxMDEiIGQ9Ik0zMy4yNSAzMS42NTJBMTkuMDE1IDE5LjAxNSAwIDAgMCAzOCAxOS4wNkMzOCA4LjU0OSAyOS40NzggMCAxOSAwUzAgOC41NSAwIDE5LjA1OWMwIDQuODIzIDEuNzk1IDkuMjMzIDQuNzUgMTIuNTkzTDE4Ljk3NSA0NiAzMy4yNSAzMS42NTJ6Ij48L3BhdGg+PHBhdGggZmlsbD0iIzZBNkQ3NCIgZmlsbC1vcGFjaXR5PSIuNSIgZD0iTTI2Ljg2MiAzNy41bDQuNzE0LTQuNzdjMy44MjItMy41NzYgNS45MjQtOC40MTEgNS45MjQtMTMuNjJDMzcuNSA4Ljg0NyAyOS4yLjUgMTkgLjVTLjUgOC44NDguNSAxOS4xMWMwIDUuMjA5IDIuMTAyIDEwLjA0NCA1LjkxOSAxMy42MTRsNC43MTkgNC43NzZoMTUuNzI0ek0xOSAwYzEwLjQ5MyAwIDE5IDguNTI1IDE5IDE5LjA0MSAwIDUuNTA3LTIuMzQ4IDEwLjQ1NC02LjA3OSAxMy45MzJMMTkgNDYgNi4wNzkgMzIuOTczQzIuMzQ4IDI5LjQ5NSAwIDI0LjU0OCAwIDE5LjA0IDAgOC41MjUgOC41MDcgMCAxOSAweiI+PC9wYXRoPjwvZz48L3N2Zz4K"
+ },
+ {
+ name: "greenIcon",
+ url:
+ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOCIgaGVpZ2h0PSI0NyIgdmlld0JveD0iMCAwIDM4IDQ3Ij48ZyBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjMEYxNjIxIiBmaWxsLW9wYWNpdHk9Ii40IiBkPSJNMTUgNDZjMCAuMzE3IDEuNzkuNTc0IDQgLjU3NHM0LS4yNTcgNC0uNTc0YzAtLjMxNy0xLjc5LS41NzQtNC0uNTc0cy00IC4yNTctNCAuNTc0eiI+PC9wYXRoPjxwYXRoIGZpbGw9IiMwNGI2MDEiIGQ9Ik0zMy4yNSAzMS42NTJBMTkuMDE1IDE5LjAxNSAwIDAgMCAzOCAxOS4wNkMzOCA4LjU0OSAyOS40NzggMCAxOSAwUzAgOC41NSAwIDE5LjA1OWMwIDQuODIzIDEuNzk1IDkuMjMzIDQuNzUgMTIuNTkzTDE4Ljk3NSA0NiAzMy4yNSAzMS42NTJ6Ij48L3BhdGg+PHBhdGggZmlsbD0iIzZBNkQ3NCIgZmlsbC1vcGFjaXR5PSIuNSIgZD0iTTI2Ljg2MiAzNy41bDQuNzE0LTQuNzdjMy44MjItMy41NzYgNS45MjQtOC40MTEgNS45MjQtMTMuNjJDMzcuNSA4Ljg0NyAyOS4yLjUgMTkgLjVTLjUgOC44NDguNSAxOS4xMWMwIDUuMjA5IDIuMTAyIDEwLjA0NCA1LjkxOSAxMy42MTRsNC43MTkgNC43NzZoMTUuNzI0ek0xOSAwYzEwLjQ5MyAwIDE5IDguNTI1IDE5IDE5LjA0MSAwIDUuNTA3LTIuMzQ4IDEwLjQ1NC02LjA3OSAxMy45MzJMMTkgNDYgNi4wNzkgMzIuOTczQzIuMzQ4IDI5LjQ5NSAwIDI0LjU0OCAwIDE5LjA0IDAgOC41MjUgOC41MDcgMCAxOSAweiI+PC9wYXRoPjwvZz48L3N2Zz4K"
+ },
+ {
+ name: "blueIcon",
+ url:
+ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOCIgaGVpZ2h0PSI0NyIgdmlld0JveD0iMCAwIDM4IDQ3Ij48ZyBmaWxsPSJub25lIj48cGF0aCBmaWxsPSIjMEYxNjIxIiBmaWxsLW9wYWNpdHk9Ii40IiBkPSJNMTUgNDZjMCAuMzE3IDEuNzkuNTc0IDQgLjU3NHM0LS4yNTcgNC0uNTc0YzAtLjMxNy0xLjc5LS41NzQtNC0uNTc0cy00IC4yNTctNCAuNTc0eiI+PC9wYXRoPjxwYXRoIGZpbGw9IiMwMTgwYjYiIGQ9Ik0zMy4yNSAzMS42NTJBMTkuMDE1IDE5LjAxNSAwIDAgMCAzOCAxOS4wNkMzOCA4LjU0OSAyOS40NzggMCAxOSAwUzAgOC41NSAwIDE5LjA1OWMwIDQuODIzIDEuNzk1IDkuMjMzIDQuNzUgMTIuNTkzTDE4Ljk3NSA0NiAzMy4yNSAzMS42NTJ6Ij48L3BhdGg+PHBhdGggZmlsbD0iIzZBNkQ3NCIgZmlsbC1vcGFjaXR5PSIuNSIgZD0iTTI2Ljg2MiAzNy41bDQuNzE0LTQuNzdjMy44MjItMy41NzYgNS45MjQtOC40MTEgNS45MjQtMTMuNjJDMzcuNSA4Ljg0NyAyOS4yLjUgMTkgLjVTLjUgOC44NDguNSAxOS4xMWMwIDUuMjA5IDIuMTAyIDEwLjA0NCA1LjkxOSAxMy42MTRsNC43MTkgNC43NzZoMTUuNzI0ek0xOSAwYzEwLjQ5MyAwIDE5IDguNTI1IDE5IDE5LjA0MSAwIDUuNTA3LTIuMzQ4IDEwLjQ1NC02LjA3OSAxMy45MzJMMTkgNDYgNi4wNzkgMzIuOTczQzIuMzQ4IDI5LjQ5NSAwIDI0LjU0OCAwIDE5LjA0IDAgOC41MjUgOC41MDcgMCAxOSAweiI+PC9wYXRoPjwvZz48L3N2Zz4K"
+ }
+ ];
+
+ /**
+ * Creates a new MapView for the HTMLCanvasElement of the given id.
+ */
+ function initializeMapView(id: string): MapView {
+ const canvas = document.getElementById(id) as HTMLCanvasElement;
+ const map = new MapView({
+ canvas,
+ theme: {
+ extends: "resources/berlin_tilezen_base.json",
+ styles: {
+ // Specify the styling for the markers.
+ geojson: [
+ {
+ when: ["==", ["geometry-type"], "Point"],
+ technique: "labeled-icon",
+ imageTexture: ["get", "icon"],
+ text: ["get", "text"],
+ size: 15,
+ priority: 1000,
+ color: "black",
+ iconMayOverlap: true,
+ textMayOverlap: true,
+ renderOrder: ["get", "renderOrder"],
+ iconFadeTime: 0,
+ textFadeTime: 0
+ }
+ ]
+ }
+ },
+ target: new GeoCoordinates(52.52, 13.4),
+ zoomLevel: 12
+ });
+
+ CopyrightElementHandler.install("copyrightNotice").attach(map);
+
+ const controls = new MapControls(map);
+
+ // Add an UI.
+ const ui = new MapControlsUI(controls, { projectionSwitch: true, zoomLevel: "input" });
+ canvas.parentElement!.appendChild(ui.domElement);
+
+ window.addEventListener("resize", () => {
+ map.resize(window.innerWidth, window.innerHeight);
+ });
+
+ map.update();
+
+ return map;
+ }
+
+ function handlePick(
+ mapView: MapView,
+ markersDataSource: FeaturesDataSource,
+ x: number,
+ y: number
+ ): void {
+ // Intersection test filtering the results by layer name to get only markers.
+ const layerName = (markersDataSource.dataProvider() as GeoJsonDataProvider).name;
+ const results = mapView.intersectMapObjects(x, y).filter(result => {
+ return result.userData?.$layer === layerName;
+ });
+
+ if (results.length === 0) {
+ return;
+ }
+
+ const uuid = results[0].userData?.__mapViewUuid;
+ if (uuid !== undefined) {
+ const feature = new MapViewPointFeature([]);
+ feature.uuid = uuid;
+ markersDataSource.remove(feature);
+ }
+ }
+
+ let markerId = 0;
+ function attachClickEvents(mapView: MapView, markersDataSource: FeaturesDataSource) {
+ mapView.canvas.addEventListener("click", event => {
+ if (event.shiftKey) {
+ const geo = mapView.getGeoCoordinatesAt(event.clientX, event.clientY);
+ if (geo) {
+ // Add a new marker to the data source at the click coordinates.
+ markersDataSource.add(
+ new MapViewPointFeature(geo.toGeoPoint() as number[], {
+ text: markerId.toString(),
+ id: markerId,
+ icon: icons[markerId % icons.length].name,
+ renderOrder: markerId
+ })
+ );
+ markerId++;
+ }
+ } else if (event.ctrlKey) {
+ handlePick(mapView, markersDataSource, event.pageX, event.pageY);
+ }
+ });
+
+ window.addEventListener("keypress", event => {
+ if (event.key === "c") {
+ markersDataSource.clear();
+ markerId = 0;
+ }
+ });
+
+ const instructions = `
+Shift+Left Click to add a marker
Ctrl+Left Click to remove it
+Press 'c' to clear the map.
`;
+ const message = document.createElement("div");
+ message.style.position = "absolute";
+ message.style.cssFloat = "right";
+ message.style.top = "10px";
+ message.style.right = "10px";
+ message.style.backgroundColor = "grey";
+ message.innerHTML = instructions;
+ document.body.appendChild(message);
+ }
+
+ function attachDataSources(mapView: MapView) {
+ const omvDataSource = new VectorTileDataSource({
+ baseUrl: "https://vector.hereapi.com/v2/vectortiles/base/mc",
+ apiFormat: APIFormat.XYZOMV,
+ styleSetName: "tilezen",
+ maxDataLevel: 17,
+ authenticationCode: apikey,
+ authenticationMethod: {
+ method: AuthenticationMethod.QueryString,
+ name: "apikey"
+ },
+ copyrightInfo
+ });
+ mapView.addDataSource(omvDataSource);
+
+ // Register the icon image referenced in the style.
+ for (const { name, url } of icons) {
+ mapView.userImageCache.addImage(name, url);
+ }
+
+ // Create a [[FeaturesDataSource]] for the markers.
+ const markersDataSource = new FeaturesDataSource({
+ name: "geojson",
+ styleSetName: "geojson",
+ gatherFeatureAttributes: true
+ });
+ mapView.addDataSource(markersDataSource);
+
+ attachClickEvents(mapView, markersDataSource);
+ }
+
+ const mapView = initializeMapView("mapCanvas");
+ attachDataSources(mapView);
+}