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!
Related
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
so I'm making a website using leaflet with dozens of base maps. I want to incorporate information about each map that is only visible if the user wants it. To do this, I would like to make an overlay map with popups, but I want the popups to change depending on the base map selected by the user.
How would I go about doing this?
Thank You So Much
You need to either use a plugin that keeps track of the base maps for you (like active layers) or you need to do it yourself.
If you are using the Leaflet layers control, you can subscribe to the basemapchange event to do this easily.
You need two things: active base layer management (easy) and dynamic popups (not too hard)
To wit:
First, here is the event handler to track active base layer when it changes.
map.on("baselayerchange",
function(e) {
// e.name has the layer name
// e.layer has the layer reference
map.activeBaseLayer = e.layer;
console.log("base map changed to " + e.name);
});
Because using L.marker().bindPopup() creates the popup content right there and does not support callbacks, you must manually create the popups in response to click event by calling map.openPopup() with your dynamic html (dynamic because it uses a variable: the active basemap name)
marker.on("click", function(e) {
var html = "Current base layer: <br/><b>" + map.activeBaseLayer.options.name + "<b>";
map.openPopup(html,
e.latlng, {
offset: L.point(1, -24)
});
});
Here is a working example on JS fiddle: http://jsfiddle.net/4caaznsc/
Working code snippet also below (relies on Leaflet CDN):
// Create the map
var map = L.map('map').setView([39.5, -0.5], 5);
// Set up the OSM layer
var baseLayer1 = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
name: "Base layer 1"
});
var baseLayer2 = L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
name: "Base layer 2"
});
// add some markers
function createMarker(lat, lng) {
var marker = L.marker([lat, lng]);
marker.on("click", function(e) {
var html = "Current base layer: <br/><b>" + map.activeBaseLayer.options.name + "<b>";
map.openPopup(html,
e.latlng, {
offset: L.point(1, -24)
});
});
return marker;
}
var markers = [createMarker(36.9, -2.45), createMarker(36.9, -2.659), createMarker(36.83711, -2.464459)];
// create group to hold markers, it will be added as an overlay
var overlay = L.featureGroup(markers);
// show overlay by default
overlay.addTo(map);
// show features
map.fitBounds(overlay.getBounds(), {
maxZoom: 11
});
// make up our own property for activeBaseLayer, we will keep track of this when it changes
map.activeBaseLayer = baseLayer1;
baseLayer1.addTo(map);
// create basemaps and overlays collections for the layers control
var baseMaps = {};
baseMaps[baseLayer1.options.name] = baseLayer1;
baseMaps[baseLayer2.options.name] = baseLayer2;
var overlays = {
"Overlay": overlay
};
// create layers control
var layersControl = L.control.layers(baseMaps, overlays).addTo(map);
// update active base layer when changed
map.on("baselayerchange",
function(e) {
// e.name has the name, but it may be handy to have layer reference
map.activeBaseLayer = e.layer;
map.closePopup(); // any open popups will no longer be correct; take easy way out and hide 'em
});
#map {
height: 400px;
}
<script src="https://npmcdn.com/leaflet#0.7.7/dist/leaflet.js"></script>
<link href="https://npmcdn.com/leaflet#0.7.7/dist/leaflet.css" rel="stylesheet"/>
<div id="map"></div>
I am using open layers 3,
and I am using this code for displaying the map:
wmsSource = new ol.source.TileWMS({
url: 'http://demo.boundlessgeo.com/geoserver/wms',
params: { 'LAYERS': 'ne:ne' },
serverType: 'geoserver',
crossOrigin: ''
});
var wmsLayer = new ol.layer.Tile({
source: wmsSource
});
I am using dragbox to make the rectangular selection and when I do the shift + drag I am not able to select the objects in map. Can somebody please help me on how to achieve it?
This is the code I am using for rectangular selection.
dragBox.on('boxend', function(e) {
// features that intersect the box are added to the collection of
// selected features, and their names are displayed in the "info"
// div
var info = [];
var extent = dragBox.getGeometry().getExtent();
wmsSource .forEachFeatureIntersectingExtent(extent, function(feature) {
selectedFeatures.push(feature);
info.push(feature.get('name'));
});
if (info.length > 0) {
infoBox.innerHTML = info.join(', ');
}
}); `
You use a TileWMS source, which is a collection of images (tiles) rendered on the WMS server. OpenLayers does not know about the features used to render the images. Because of this, forEachFeatureIntersectingExtent is only available on vector sources.
You could create a WMS getFeatureInfo-request in the boxend callback, to load the feature information from the server.
Alternatively, you could create a vector source containing the features you want and use for the forEachFeatureIntersectingExtent call.
I use the following block of JavaScript to try to show a WMS layer. I'm using OpenLayers 2.8.
The map's base layer (Openstreetmap) shows correctly, it zooms to the correct area, the "pyramid" layer is shown in the layer switcher, but no request to its WMS service is ever made (so the fact that the URL, styles and params are dummies shouldn't matter -- it never even attempts to get them).
OpenLayers does try to get a WMS layer once I pan or zoom far enough so that the Gulf of Guinea is in view (but all my data is in the Netherlands). This suggests a projection problem (WGS84's (0, 0) point is there), but I don't understand why OpenLayers doesn't even try to fetch a map layer elsewhere. My data is in EPSG:3857 (Web Mercator) projection.
/*global $, OpenLayers */
(function () {
"use strict";
$(function () {
$(".map").each(function () {
var div = $(this);
var data_bounds = div.attr("data-bounds");
console.log("data_bounds: " + data_bounds);
if (data_bounds !== "") {
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857"});
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
map.addLayer(
new OpenLayers.Layer.OSM(
"OpenStreetMap NL",
"http://tile.openstreetmap.nl/tiles/${z}/${x}/${y}.png",
{buffer: 0}));
map.addLayer(
new OpenLayers.Layer.WMS(
"pyramid", "http://rasterserver.local:5000/wms", {
layers: "test",
styles: "test"
}, {
singleTile: true,
isBaseLayer: false,
displayInLayerSwitcher: true,
units: 'm'
}));
map.addControl(new OpenLayers.Control.LayerSwitcher());
map.zoomToExtent(bounds);
}
});
});
})();
Edit: the 'data_bounds' console print prints (with some added formatting):
data_bounds: {
"minx": 582918.5701295201,
"miny": 6923595.841021758,
"maxx": 821926.9006116659,
"maxy": 7079960.166533174
}
It zooms to the correct region in the north of the Netherlands, so I don't think the problem is there.
Since posting, I found out that if I don't use the OSM layer, and instead use the WMS layer as baselayer, it works. So perhaps there's some incompatibility with a OSM baselayer and a WMS layer added to it? But then I don't get that it does seem to do something near WGS84 (0, 0).
I eventually managed to fix this by giving the map an explicit maxExtent:
var extent = JSON.parse(data_bounds);
var bounds = new OpenLayers.Bounds(
extent.minx, extent.miny,
extent.maxx, extent.maxy);
var map = new OpenLayers.Map(div.attr("id"), {
projection: "EPSG:3857",
maxExtent: bounds
});
Oddly enough this doesn't limit the user's ability to pan and zoom around the world, but it does make the overlay work...
I am attempting what I imagine to be a fairly common use-case with a leaflet multipolygon object.
I create the MultiPolygon using geojson:
var layer = L.GeoJSON(g, style_opts);
What I'd like is to put a simple text label in the center of each polygon. (For example, something like putting state name in the center of each state).
I've looked at:
https://groups.google.com/forum/?fromgroups=#!topic/leaflet-js/sA2HnU5W9Fw
Which actually overlays the text, but when I add a bunch of polygons, it appears to put the label off-center in weird ways, and I'm currently unable to track down the problem.
I've also looked at: https://github.com/jacobtoye/Leaflet.label
but that appears to only put the label on polygons when you mouse over the polygon, and does not stay statically on the polygon.
I think my best course of action is to use that first link, and track down why it's changing the location, but in the meantime, if anyone knows of a quick and easy way to lay a label on a polygon in leaflet, I'd be much obliged.
Also, if I have any faulty assumptions about the two links above, please feel free to straighten me out.
Thanks very much in advance.
The leaflet label plugin also allows static labels, see the demo.
The only reason the polyline label is not static is that it moves around as you move along the polyline.
You can probably do better than this, by overriding bindLabel() for Polygons but this is a simple way to add a static label to the center of a polygon:
label = new L.Label()
label.setContent("static label")
label.setLatLng(polygon.getBounds().getCenter())
map.showLabel(label);
http://jsfiddle.net/CrqkR/6/
You can use the onEachFeature option of L.geoJson to create a new L.divIcon for each polygon.
L.geoJson(geoJsonData, {
onEachFeature: function(feature, layer) {
var label = L.marker(layer.getBounds().getCenter(), {
icon: L.divIcon({
className: 'label',
html: feature.properties.NAME,
iconSize: [100, 40]
})
}).addTo(map);
}
);
you can use this code to show label in polygon :
var eu = L.geoJSON(euCountries, {
onEachFeature: function (feature, layer) {
// if (feature.geometry.type === "Polygon") {
var bounds = layer.getBounds();
// Get center of bounds
var center = bounds.getCenter();
//var center = layer.getBounds().getCenter();
if(feature.properties.name=="russia")
{
alert(center)
}
layer.bindTooltip(feature.properties.name, {permanent: true, direction: "center", className: "my-labels"});
layer.on("click", function (e) {
layer.bindPopup(feature.properties.name);
});
/* var marker =L.circleMarker(center, {color: '', radius:10,Title:20}).bindTooltip(feature.properties.name, {permanent: true, direction: "center", className: "my-labels"});
map.addLayer(marker);*/
// var polygonAndItsCenter = L.layerGroup([layer, marker]);
// }
},
});
eu.addTo(map);