Azure Maps SDK popup does not open after initial load - javascript

I am using Azure Maps SDK in a Blazor Server app which uses JavaScript interop. The map works well and renders perfectly.
On initial load of the map component the popup displays correctly when clicking a pin on the map. However, when the data source changes via an API call datasource.importDataFromUrl(organisationDataFeed); it correctly brings back the filtered data, but when I click on one of the pins again the popup does not display. The click event handler (for the pin) is being called, but the popup does not open.
(function () {
var map, datasource, popup;
// Global export
window.azuremaps = {
initMap: function (organisationDataFeed) {
// Create an instance of the map control and set some options.
map = new atlas.Map('map', {
// center: [-97, 39],
// center: [18.424095, -33.925000],
center: [26.157981, -29.083937],
zoom: 4,
pitch: 50,
style: 'night',
view: 'Auto',
// Add your Azure Maps subscription key to the map SDK. Get an Azure Maps key at https://azure.com/maps
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: ''
}
});
map.controls.add([
new atlas.control.ZoomControl(),
new atlas.control.CompassControl(),
new atlas.control.PitchControl(),
new atlas.control.StyleControl()
], {
position: "top-right"
});
// Wait until the map resources are ready.
map.events.add('ready', function () {
// Create a data source and add it to the map.
datasource = new atlas.source.DataSource(null, {
//Tell the data source to cluster point data.
cluster: true,
//The radius in pixels to cluster points together.
clusterRadius: 45,
// The maximium zoom level in which clustering occurs.
// If you zoom in more than this, all points are rendered as symbols.
clusterMaxZoom: 15
});
map.sources.add(datasource);
//Create a bubble layer for rendering clustered data points.
var clusterBubbleLayer = new atlas.layer.BubbleLayer(datasource, null, {
//Scale the size of the clustered bubble based on the number of points inthe cluster.
radius: [
'step',
['get', 'point_count'],
20, //Default of 20 pixel radius.
100, 30, //If point_count >= 100, radius is 30 pixels.
750, 40 //If point_count >= 750, radius is 40 pixels.
],
//Change the color of the cluster based on the value on the point_cluster property of the cluster.
color: [
'step',
['get', 'point_count'],
'rgba(0,255,0,0.8)', //Default to green.
100, 'rgba(255,255,0,0.8)', //If the point_count >= 100, color is yellow.
750, 'rgba(255,0,0,0.8)' //If the point_count >= 100, color is red.
],
strokeWidth: 0,
filter: ['has', 'point_count'] //Only rendered data points which have a point_count property, which clusters do.
});
//Add a click event to the layer so we can zoom in when a user clicks a cluster.
map.events.add('click', clusterBubbleLayer, clusterClicked);
//Add mouse events to change the mouse cursor when hovering over a cluster.
map.events.add('mouseenter', clusterBubbleLayer, function () {
map.getCanvasContainer().style.cursor = 'pointer';
});
map.events.add('mouseleave', clusterBubbleLayer, function () {
map.getCanvasContainer().style.cursor = 'grab';
});
// Create a layer to render the individual locations.
var individualSymbolLayer = new atlas.layer.SymbolLayer(datasource, null, {
filter: ['!', ['has', 'point_count']], //Filter out clustered points from this layer.
textOptions: {
textField: ['get', 'name'],
color: "#FFFFFF",
offset: [0, -2.2]
},
});
map.events.add('click', individualSymbolLayer, symbolClicked);
//Add the clusterBubbleLayer and two additional layers to the map.
map.layers.add([
clusterBubbleLayer,
// Create a symbol layer to render the count of locations in a cluster.
new atlas.layer.SymbolLayer(datasource, null, {
iconOptions: {
image: 'none' //Hide the icon image.
},
textOptions: {
textField: ['get', 'point_count_abbreviated'],
offset: [0, 0.4]
}
}),
individualSymbolLayer
]);
// Create a popup but leave it closed so we can update it and display it later.
popup = new atlas.Popup({
pixelOffset: [0, -18],
closeButton: true
});
// Retrieve a GeoJSON data set and add it to the data source.
datasource.importDataFromUrl(organisationDataFeed);
});
},
};
function clusterClicked(e) {
if (e && e.shapes && e.shapes.length > 0 && e.shapes[0].properties.cluster) {
// Get the clustered point from the event.
var cluster = e.shapes[0];
// Get the cluster expansion zoom level. This is the zoom level at which the cluster starts to break apart.
datasource.getClusterExpansionZoom(cluster.properties.cluster_id).then(function (zoom) {
//Update the map camera to be centered over the cluster.
map.setCamera({
center: cluster.geometry.coordinates,
zoom: zoom + 2,
type: 'ease',
duration: 200
});
});
}
}
function symbolClicked(e) {
// Make sure the event occured on a point feature.
var popupTemplate = '<div class="card border-success" style="visibility: visible"><div class="card-header" style="visibility: visible">{name}</div><div class="card-body text-success" style="visibility: visible"><h5 class="card-title" style="visibility: visible">{description}</h5><p class="card-text" style="visibility: visible">Contact: {contact}</p><p class="card-text">Web: {website}</p></div></div>';
if (e.shapes && e.shapes.length > 0) {
var content, coordinate;
// Check to see if the first value in the shapes array is a Point Shape.
if (e.shapes[0] instanceof atlas.Shape && e.shapes[0].getType() === 'Point') {
var properties = e.shapes[0].getProperties();
content = popupTemplate.replace(/{name}/g, properties.name).replace(/{description}/g, properties.description).replace(/{contact}/g, properties.contact).replace(/{website}/g, properties.website);
coordinate = e.shapes[0].getCoordinates();
}
else if (e.shapes[0].type === 'Feature' && e.shapes[0].geometry.type === 'Point') {
// Check to see if the feature is a cluster.
if (e.shapes[0].properties.cluster) {
content = '<div style="padding:10px;">Cluster of ' + e.shapes[0].properties.point_count + ' symbols</div>';
} else {
// Feature is likely from a VectorTileSource.
content = popupTemplate.replace(/{name}/g, properties.name).replace(/{description}/g, properties.description).replace(/{contact}/g, properties.contact).replace(/{website}/g, properties.website);
}
coordinate = e.shapes[0].geometry.coordinates;
}
if (content && coordinate) {
// Populate the popupTemplate with data from the clicked point feature.
console.log("JB content");
console.log(content);
console.log("JB coordinate");
console.log(coordinate);
popup.setOptions({
//Update the content of the popup.
content: content,
//Update the position of the popup with the symbols coordinate.
position: coordinate
});
console.log("JB: logging map variable");
console.log(map);
popup.open(map);
}
}
}
})();
content and coordinate are populated with values and evaluate to true, the options are correctly set, but just the last line: popup.open(map); does not work if the data source changes bringing back new data into the map. It works perfectly on initial load.
Any ideas what I could do to get this working? Thanks

A couple of things to try:
Double check the values of coordinate and content. The coordinates should be an array with [longitude,latitude] numbers (make sure they aren't string values of numbers). The content should either be a DOMElement (i.e. div), or a string. If it is anything else, it may not display anything.
Double check the "map" variable is a map the second time around. If the reference is lost for some reason, that function won't work.
It looks like you are create a new popup all the time. Often apps only want to display a single popup at a time. In that case it is more efficient to create a single popup and reuse it as shown in this example: https://azuremapscodesamples.azurewebsites.net/index.html?sample=Reusing%20Popup%20with%20Multiple%20Pins

Related

OpenLayers Inconsistent Hit Detection on MVT Layer Hover Selection

The objective
I'm trying to replicate the "singleselect-hover" feature in this example from the OpenLayers site.
The issue
When I tried to use this implementation, the hit detection was very poor with vtLayer.getFeatures(event.pixel). The documentation for the function states:
The hit detection algorithm used for this method is optimized for performance, but is less accurate than the one used in map.getFeaturesAtPixel()
Indeed, when I switched to map.getFeaturesAtPixel, the performance increased, but the features still does not work entirely as expected.
When I move my pointer over a vector boundary from the outside, it (usually) behaves as expected:
However, when I move to an adjacent boundary and then back, the feature no longer works:
My code:
proj4.defs(
'EPSG:6931',
'+proj=laea +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'
);
register(proj4);
const projection = get('EPSG:6931');
const osm = new OSM();
const map = new Map({
target: something,
layers: [osm],
view: new View({
projection: projection,
zoom: 5,
}),
})
// Vector Styles
const unselectedStyle = new Style({
stroke: new Stroke({
color: 'rgba(50,50,50,0.9)',
width: 1.2,
}),
});
const selectedStyle = new Style({
stroke: new Stroke({
color: 'white',
width: 2,
}),
fill: new Fill({
color: 'rgba(0,0,0,0.3)',
}),
});
const createVtLayer = () => {
const vtSource = new VectorTileSource({
tileGrid: new TileGrid({
extent: [
-9009964.761231285, -9009964.761231285, 9009964.761231285,
9009964.761231285,
],
tileSize: 256,
resolutions: [70390.34969711941],
}),
projection: projection,
format: new MVT({ idProperty: 'some_id' }),
url:
geoserverUrl +
'/gwc/service/tms/1.0.0/' +
mvtLayerName +
'#EPSG%3A' +
projection.getCode().split(':')[1] + // EPSG number of current projection
'#pbf/{z}/{x}/{-y}.pbf',
});
return new VectorTileLayer({
zIndex: 1,
source: vtSource,
style: unselectedStyle,
});
};
const vtLayer = createVtLayer();
// Local lookup for highlighted features
let selection = null;
const selectionLayer = new VectorTileLayer({
zIndex: 2,
source: vtLayer.getSource(),
style: feature => feature === selection && selectedStyle,
});
if (map && vtLayer && selectionLayer) {
// Add layers to map once loaded into state
map.addLayer(vtLayer);
map.addLayer(selectionLayer);
// Update styling of selectionLayer on mouse hover
map.on(['pointermove', 'click'], event => {
// Send vector metadata to parent component on mouse click
if (event.type === 'click' && selection) {
onFeatureSelect(selection);
}
map.forEachFeatureAtPixel(
event.pixel,
feature => {
selection = feature;
}, {
layerFilter: layer => layer === vtLayer,
hitTolerance: 1,
}
);
selectionLayer.changed()
});
}
What I've tried so far
I've tried adjusting the renderBuffer and renderMode parameters in the VectorTile layer, as well as adjusting the hitTolerance option in map.forEachFeatureAtPixel, and I haven't had any luck yet. When I logged the feature id from the feature parameter in forEachFeatureAtPixel, I noticed something strange--when the unexpected behavior occurs, after dragging the pointer over an adjacent boundary line, the selected variable rapidly switches between the two features and assigns the undesired one, until I touch the pointer to a non-adjacent boundary line. Modifying the hitTolerance only causes the selected feature to switch more frequently.
Theories and questions
I'm thinking that maybe my adjacent vectors are overlapping each others boundaries? Or maybe there is a problem with the way the vectors are loaded as MVTs?
Adding an invisible Fill() to the unselectedStyle allowed the layer to be hit-detected and solved my issue!
// Vector Styles
const unselectedStyle = new Style({
stroke: new Stroke({
color: 'rgba(50,50,50,0.9)',
width: 1.2,
}),
fill: new Fill({
color: 'rgba(0,0,0,0)',
}),
});
Thanks Mike!

How to use FitBounds in deckgl on timer without npm and es6

I've a deckGL map in a div-container.
let deckMap = new deck.DeckGL({
mapStyle: 'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json',
initialViewState: {..
},
layers: [geoJSONLayer],
getTooltip,
controller: true,
onViewStateChange: ({ viewState }) => {
console.log("View Change");
deckMap.setProps({ viewState })
}
});
That thing "overlays"(?) my normal mapgl map
const map = new mapboxgl.Map({
container: 'map',
interactive: false,
style: carto.basemaps.voyager,
center: [INITIAL_VIEW_STATE.longitude, INITIAL_VIEW_STATE.latitude],
zoom: INITIAL_VIEW_STATE.zoom
});
I'm running a cyclic download of a GeoJSON file and want to display the data. that already works. I also can calculate the bounds using bounds.extend.
But I can't find a way to set the zoom/fit the bounds.
What I've tried
map.fitBounds is just not working. It does nothing
deckMap.fitBound => unknown command
WebMercatorViewport => needs an import, import is unknown, including it requires app.js to be a module which screws the whole code.
viewport=info.context with that I could set viewport.fitBounds. Problem: What is info? I found this example in an event driven approach and info is the layer that the user interacts with. But simply using my geoJSONLayer does not work; fitBounds is not existent
Calculate zoom by hand. Can't find the algorithm anymore, but it was for GoogleEarth and I think it was broken in general.
How do I get the damned fitBounds working or - alternatively calculate the zoom for deckGL? I wasted 5 hours on that today!
There is a working example of deck.gl fitBounds on map initialization here. To run:
Clone this repository;
Navigate into the repository and run npm i && npm start;
Go to http://localhost:8080/ and click the Toggle deck.gl mode button at the top. This will load a deck.gl instance and fit the map bounds to the data.
This gist helped me get things working:
https://gist.github.com/tomsoderlund/a2040d659aafe4064e4060f561aca6d1
Steps:
In case someone else will find this useful, the fit bounds process can be started with an event. In the case of the question, when a new geoJSON has been successfully fetched. This could also be triggered when a user clicks an auto-fit map to visible data button, or in the example above, on deck.gl initialization, etc...
Next, a helper function is used to calculate opposite corners of your bounds by passing an array of point type coordinates of data visible on your map:
fitBounds = (coords) => {
let latMin = 90;
let latMax = -90;
let lonMin = 180;
let lonMax = -180;
coords.forEach(function (coord) {
const RECT_LAT_INDEX = 'lat';
const RECT_LON_INDEX = 'lon';
if (coord[RECT_LAT_INDEX] < latMin) latMin = coord[RECT_LAT_INDEX];
if (coord[RECT_LAT_INDEX] > latMax) latMax = coord[RECT_LAT_INDEX];
if (coord[RECT_LON_INDEX] < lonMin) lonMin = coord[RECT_LON_INDEX];
if (coord[RECT_LON_INDEX] > lonMax) lonMax = coord[RECT_LON_INDEX];
});
const bounds = [
[lonMin, latMax],
[lonMax, latMin],
];
return bounds;
}
Create an instance of WebMercatorViewport and pass the same height and width parameters as your map;
const view = new WebMercatorViewport({ width: 800, height: 600 });
Call the fitBounds function on this WebMercatorViewport instance. The response will contain latitude, longitude and zoom attributes so I've destructured them below:
const { latitude, longitude, zoom } = view.fitBounds(bounds)
Set the latitude, longitude, zoom attriubtes on your map view state:
const INITIAL_VIEW_STATE = {
latitude,
longitude,
zoom,
maxZoom: 20,
pitch: 45,
bearing: 0,
};
The map can then be rendered with something like:
return (
<DeckGL
layers={layers}
initialViewState={INITIAL_VIEW_STATE}
controller={true}
>
<div style={COPYRIGHT_LICENSE_STYLE}>
{"© "}
<a
style={LINK_STYLE}
href="http://www.openstreetmap.org/copyright"
target="blank"
>
OpenStreetMap contributors
</a>
</div>
</DeckGL>
);
Padding can be added like below so that it fits nicely onto your map, something like:
const { latitude, longitude, zoom } = new WebMercatorViewport({ width: 800, height: 600 }).fitBounds(bounds, { padding: { top: 100, bottom: 100, left: 100, right: 100 } })

Highlight feature with effects and blending in Arcgis JavaScript Api

I am using this sample, which uses layer blending and effect on multiple layers to create a more artistic way of highlighting features selected by the user. When the application loads, the basemap is displayed over all other layers in the map. When a user clicks on a country, the country will be highlighted.
The light gray canvas vector tiles layer and a countries feature layer are added to a group layer and the destination-over blendMode is set on the group layer. With the destination-over blendMode, the background layer covers, or is placed over, the top layer. The content of the top layer is visible where both layers do not overlap. When the app loads, you see the modern antique vector tiles basemap on top of the groupLayer.
My question is, is there a way to do the same with Feature Access instead of using portal ID.
<html>
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>
Highlight feature with effects and blending | Sample | ArcGIS API for
JavaScript 4.21
</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#messageDiv {
padding-left: 10px;
padding-right: 10px;
}
</style>
<link
rel="stylesheet"
href="https://js.arcgis.com/4.21/esri/themes/light/main.css"
/>
<script src="https://js.arcgis.com/4.21/"></script>
<script>
require([
"esri/WebMap",
"esri/views/MapView",
"esri/layers/VectorTileLayer",
"esri/Graphic",
"esri/layers/FeatureLayer",
"esri/layers/GraphicsLayer",
"esri/layers/GroupLayer"
], (
WebMap,
MapView,
VectorTileLayer,
Graphic,
FeatureLayer,
GraphicsLayer,
GroupLayer
) => {
const map = new WebMap({
basemap: {
portalItem: {
id: "f35ef07c9ed24020aadd65c8a65d3754" // modern antique vector tiles
}
}
});
const vtLayer = new VectorTileLayer({
portalItem: {
id: "2afe5b807fa74006be6363fd243ffb30" // gray vector tiles canvas
}
});
const countries = new FeatureLayer({
portalItem: {
id: "53a1e68de7e4499cad77c80daba46a94" // boundaries of countries
}
});
// This group layer groups the gray canvas vector tiles and
// countries feature layer.
// With destination-over blendMode, the background layer covers
// the top layer. The top layer is put behind the destination layer.
// So when the app starts, the basemap layer will be shown over this layer
const groupLayer = new GroupLayer({
layers: [vtLayer, countries],
blendMode: "destination-over"
});
map.add(groupLayer);
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 6,
center: [2, 46],
constraints: {
snapToZoom: false,
minScale: 147914381
}
});
let layerView, animation;
// countryGraphicsLayer is added to the view's basemap.
// It will contain black polygon covering the extent of the world
// the country graphic will also be added to this layer when user clicks a country.
// With destination-in blend mode, the contents of background layer is
// kept where it overlaps with top layer. Everything else is made transparent.
// In this case, the countryGraphicsLayer will be displayed underneath
// modern antique vector tiles basemap.
// The bloom effect will add a glow around the selected country.
const countryGraphicsLayer = new GraphicsLayer({
blendMode: "destination-in",
effect: "bloom(200%)"
});
map.loadAll().then(async () => {
addWorld();
map.basemap.baseLayers.getItemAt(1).blendMode = "multiply";
// add the buffer graphicslayer to the basemap
map.basemap.baseLayers.add(countryGraphicsLayer);
// get a reference ot the countries featurelayer's layerview
// layerview will be queried to get the intersecting country
// when user clicks on the map
layerView = await view.whenLayerView(countries);
});
view.ui.add("messageDiv", "top-right");
const symbol = {
type: "simple-fill",
color: "white",
outline: null
};
// listen to the view's click event
view.on("click", async (event) => {
// query the countries featurelayer for a country that intersects the point
// user clicked on
const {
features: [feature]
} = await layerView.queryFeatures({
geometry: view.toMap(event),
returnGeometry: true,
maxAllowableOffset: 10000,
outFields: ["*"]
});
countryGraphicsLayer.graphics.removeAll();
animation && animation.remove();
let world = addWorld();
// add the clicked country feature to the graphicslayer
if (feature) {
feature.symbol = symbol;
countryGraphicsLayer.graphics.add(feature);
// add a fade animation to show the highlight effect
// for the selected country
animation = fadeWorld(world);
// zoom to the highlighted country
view.goTo(
{
target: view.toMap(event),
extent: feature.geometry.extent.clone().expand(1.8)
},
{ duration: 1000 }
);
}
});
function addWorld(world) {
world = new Graphic({
geometry: {
type: "extent",
xmin: -180,
xmax: 180,
ymin: -90,
ymax: 90
},
symbol: {
type: "simple-fill",
color: "rgba(0, 0, 0, 1)",
outline: null
}
});
countryGraphicsLayer.graphics.add(world);
return world;
}
// add a fading animation when user clicks on a country
function fadeWorld(world) {
let timer;
// requestAnimationFrame method specifies "frame" function
// to perform an animation where the opacity of the world polygon graphic
// decreased by 0.1 until it is 0 or completely transparent
// then the animation is cancelled
function frame() {
const symbol = world.symbol.clone();
symbol.color.a = Math.max(0, symbol.color.a - 0.1);
world.symbol = symbol;
if (symbol.color.a > 0) {
timer = requestAnimationFrame(frame);
}
}
frame();
return {
remove() {
cancelAnimationFrame(timer);
}
};
}
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="messageDiv" class="esri-widget esri-heading">
<h4 class="esri-heading">Click on a country</h4>
</div>
</body>
</html>

Mapbox-gl line layer from source as fill layer

Mapbox GL JS w/ w3w grid
Hi, I'm just playing around with the What3Words grid on Mapbox code from the tutorial. (https://developer.what3words.com/tutorial/displaying-the-what3words-grid-on-a-mapbox-map)
I'm trying to make the tiles from the grid interactive, kind of like in the w3w website (clickable, hover effect, getting data from them, etc), but the grid doesn't seem to work when the data source is loaded as a 'fill' layer on Mapbox, it only works as a 'line' layer type. Every single example I find online uses Polygons (or MultiPolygons) from a fill layer type, but I can't see nothing around with bounding boxes.
(Basically trying to achieve something like this, but with every tile instead of the states: https://docs.mapbox.com/mapbox-gl-js/example/hover-styles/)
I don't really know what's going on, why can't I add the source data as a fill layer? Is there a way to load the data as Polygons instead of bounding boxes?
Thanks.
Code (from the tutorial):
<html>
<head>
<script src="https://assets.what3words.com/sdk/v3.1/what3words.js?key=YOUR_API_KEY"></script>
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.js"></script>
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css" rel="stylesheet" />
<style>
#map {
height: 100%;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
// Create the Mapbox
mapboxgl.accessToken = "YOUR_MAPBOX_TOKEN";
let map = new mapboxgl.Map({
container: "map", // container id
style: "mapbox://styles/mapbox/streets-v9", // stylesheet location
center: [-0.195499, 51.52086], // starting position [lng, lat]
zoom: 18 // starting zoom
});
map.addControl(new mapboxgl.NavigationControl());
</script>
<script>
function drawGrid() {
const zoom = map.getZoom();
const loadFeatures = zoom > 17;
if (loadFeatures) { // Zoom level is high enough
var ne = map.getBounds().getNorthEast();
var sw = map.getBounds().getSouthWest();
// Call the what3words Grid API to obtain the grid squares within the current visble bounding box
what3words.api
.gridSectionGeoJson({
southwest: {
lat: sw.lat, lng: sw.lng
},
northeast: {
lat: ne.lat, lng: ne.lng
}
}).then(function(data) {
// Get the grid source from the map (it won't exist initally)
var grid = map.getSource('grid');
if (grid === undefined) {
// Create a source of type 'geojson' which loads the GeoJSON returned from the what3words API
map.addSource('grid', {
type: 'geojson',
data: data
});
// Create a new layer, which loads data from the newly created data source
map.addLayer({
id: 'grid_layer',
type: "line",
source: 'grid',
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": '#777',
"line-width": .5
}
});
} else {
// The source and map layer already exist, so just update the source data to be the new
// GeoJSON returned from the what3words API
map.getSource('grid').setData(data);
}
}).catch(console.error);
}
// If we have reached the required zoom level, set the 'grid_layer' to be visible
var grid_layer = map.getLayer('grid_layer');
if (typeof grid_layer !== 'undefined') {
map.setLayoutProperty('grid_layer', 'visibility', loadFeatures ? 'visible' : 'none');
}
}
// When the map is either loaded or moved, check to see if the grid should be draw
// if the appropriate zoom level has been met, and if so draw it on.
map
.on('load', drawGrid)
.on('move', drawGrid);
</script>
</body>
</html>

MapBox draw Polygon of Feature on Click

So my idea seems pretty straight forward to me but I struggle nevertheless. What I want to do is basically click on any point of my map and draw a polygon on the main feature, i.e. if I click on a park or a building that specific polygon is displayed and highlighted.
I used a lot of this code: https://www.mapbox.com/mapbox-gl-js/example/queryrenderedfeatures-around-point/
But instead of giving it a set of geojson I want my javascript to select to needed geojson data on mousover (eventhough i am not sure whether that works in general). Right now my code snipped compiles but doesn't show anything.
In a later step I want to collect all polygons of the same feature, i.e. all parks, and display them as highlighted polygons and then export them as a svg file which only consists of the map representations of the feature clicked on. Maybe someone has an idea for that as well?
Thanks in regard :)
This is my javascript as of now:
//Set AccessToken from MapBox
mapboxgl.accessToken = 'pk.eyJ1IjoidG1pbGRuZXIiLCJhIjoiY2o1NmlmNWVnMG5rNzMzcjB5bnV3YTlnbiJ9.r0BCga0qhRaHh0CnDdcGBQ';
//Setup starting view point at Uni-Bremen campus
var map = new mapboxgl.Map({
container: 'content-map',
style: 'mapbox://styles/mapbox/streets-v9',
center: [8.85307, 53.10810],
zoom: 16
});
//Add a search bar -> hidden for presentation
/*map.addControl(new MapboxGeocoder({
accessToken: mapboxgl.accessToken
}));*/
//Function to show all Features of a certian point
map.on('mousemove', function (e) {
var features = map.queryRenderedFeatures(e.point);
document.getElementById('features').innerHTML = JSON.stringify(features, null, 2);
console.log(JSON.stringify(features, null, 2));
drawPolygon();
});
//Draw a Polygon
function drawPolygon () {
//set boundary box as 5px rectangle area around clicked point
var bbox = [[e.point.x - 5, e.point.y - 5], [e.pont.x + 5, e.point.y + 5]];
//set the data on pointer using the bbox
var data = map.queryRenderedFeatures(bbox);
map.on('load', function() {
var dataSource = 'school';
//set school to the feature and use 'setJsonData' as data source.
map.addSource(dataSource, {
'type': 'geojson',
'data': data
});
//adding a new layer for the general display
map.addLayer({
'id': 'dataSet',
'type': 'fill',
'source': dataSource,
'source-layer': 'original',
'paint': {
'fill-outline-color': 'rgba(0,0,0,0.1)',
'fill-color': 'rgba(0,0,0,0.1)'
}
}, 'place-city-sm' ); //place polygon under these labels
//adding a new layer for the polygon to be drawn
map.addLeyer({
'id': 'dataSet-highlighted',
'type': 'fill',
'source': dataSource,
'source-layer': 'original',
'paint': {
'fill-outline-color': '#484896',
'fill-color': '#6e599f',
'fill-opacity': 0.75
},
'filter': ['in', 'FIPS', '']
}, 'place-city-sm'); //place polygon under these labels
//action on click to show the polygon and change their color
map.on('click', function (e) {
//retrieve data from 'dataSource'
var dataFromSource = map.queryRenderedFeatures(bbox, {layers: ['dataSource'] });
// Run through the selected features and set a filter
// to match features with unique FIPS codes to activate
// the `counties-highlighted` layer.
var filter = dataSource.reduce(function(memo, dataSource) {
memo.push(dataSource, properties.FIPS);
return memo;
} ['in', 'FIPS'] );
map.setFilter('dataSet-highlighted', filter);
});
});
}
I'm not 100% sure what you're asking, but my interpretation is that you'd like to specifically style certain types of geometry when you hover over them, such as "parks". You're on the right path, where using map.queryRenderedFeatures() is great. I've put together an example using the same Mapbox Streets style that queries only the building layer and looks for type university on mouseover.
When the interaction encounters a proper feature, it updates the source data with the new feature, which then updates the school-hover layer.
Check out the pen here: https://codepen.io/mapsam/pen/oemqKb
In a later step I want to collect all polygons of the same feature, i.e. all parks, and display them as highlighted polygons and then export them as a svg file which only consists of the map representations of the feature clicked on.
I won't go into file exports, but just remember that all results returned from map.queryRenderedFeatures are specific to the single vector tile you are querying, which can lead to issues on tile boundaries where a polygon isn't fully covered by your current query.
Check out this example where we are highlighting features with similar data, which should allow you get all of the necessary geometries and export to SVG.
Cheers!

Categories

Resources