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); +}