How to add a custom marker to a mapbox map that displays popups on hover? - mapbox

I apologize in advance if my question sounds stupid, but I am in way over my head here …
I am trying to create a map with mapbox with custom markers that display a popup on hover. I followed this tutorial, copied the code and added my places and descriptions, but I can’t change the URL of the custom marker. Is there a way to change the code so I can keep the hover effect, but with my custom markers?
Thank you very much!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Display a popup on hover</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<style>
.mapboxgl-popup {
max-width: 400px;
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
}
</style>
<div id="map"></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoicGllcm8xMDEiLCJhIjoiY2toNHY1ejFqMGV5ajJyczVxeHAwcHo3eCJ9.xauie-TVwLVSYj3l6AAmOw';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [-77.04, 38.907],
zoom: 11.15
});
map.on('load', function () {
map.loadImage(
'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png',
// Add an image to use as a custom marker
function (error, image) {
if (error) throw error;
map.addImage('custom-marker', image);
map.addSource('places', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'properties': {
'description':
'<strong>Make it Mount Pleasant</strong><p>Make it Mount Pleasant is a handmade and vintage market and afternoon of live entertainment and kids activities. 12:00-6:00 p.m.</p>'
},
'geometry': {
'type': 'Point',
'coordinates': [-77.038659, 38.931567]
}
},
]
}
});
// Add a layer showing the places.
map.addLayer({
'id': 'places',
'type': 'symbol',
'source': 'places',
'layout': {
'icon-image': 'custom-marker',
'icon-allow-overlap': true
}
});
}
);
// Create a popup, but don't add it to the map yet.
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
map.on('mouseenter', 'places', function (e) {
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = 'pointer';
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.description;
// Ensure that if the map is zoomed out such that multiple
// copies of the feature are visible, the popup appears
// over the copy being pointed to.
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
// Populate the popup and set its coordinates
// based on the feature found.
popup.setLngLat(coordinates).setHTML(description).addTo(map);
});
map.on('mouseleave', 'places', function () {
map.getCanvas().style.cursor = '';
popup.remove();
});
});
</script>
</body>
</html>

Your code works perfectly, including the popup on hover. To change the marker image, replace the URL used here...
map.loadImage(
'https://docs.mapbox.com/mapbox-gl-js/assets/custom_marker.png'
...to another URL, like this:
map.loadImage(
'https://i.imgur.com/kiw0dR2.png'
Here's a screenshot of your code and map before and after changing the URL.
Please note that your token pk.ey... should be kept private, not shared on public forums. To protect this token from unauthorized use, you can rotate this token apply URL restrictions so it only works on a site like CodePen or JSFiddle.

Related

How to automatically update Mapbox when a website updated

I am looking to add markers to Mapbox. These markers will contain links to other websites and I am looking to automatically plot real-time data on the map when the website updates its articles, with a link to the website + shortened version of whatever is being said on the article. Could anyone share any tips?
Can you please share what you have done so far?
You question is very general. Therefore I can only advise to not update the complete website with each change, but use jQuery and update changes incrementally.
In Mapbox, you can add layers. These layers have data sources. When you update the data sources for the layers (that already have been created), the data displayed will be updated with them.
Pleas see this example that adds a periodically updating layer:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Add live realtime data</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
mapboxgl.accessToken = "<Access Token>";
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
zoom: 0
});
var url = 'https://wanderdrone.appspot.com/';
map.on('load', function () {
var request = new XMLHttpRequest();
window.setInterval(function () {
// make a GET request to parse the GeoJSON at the url
request.open('GET', url, true);
request.onload = function () {
if (this.status >= 200 && this.status < 400) {
// retrieve the JSON from the response
var json = JSON.parse(this.response);
// update the drone symbol's location on the map
map.getSource('drone').setData(json);
// fly the map to the drone's current location
map.flyTo({
center: json.geometry.coordinates,
speed: 0.5
});
}
};
request.send();
}, 2000);
map.addSource('drone', { type: 'geojson', data: url });
map.addLayer({
'id': 'drone',
'type': 'symbol',
'source': 'drone',
'layout': {
'icon-image': 'rocket-15'
}
});
});
</script>
</body>
</html>

How can you put leaflet polylines in multiple panes?

What am I doing wrong? The intent is to have a few (say 6) leafletjs panes, each containing many markers and polylines, so the panes can be hidden independently. Here is a simplified program showing just two lines and markers in jsfiddle. The problem is, the polylines all get grouped in the same SVG and all get hidden together. Each pane in the example should show one polyline and one marker.
I don't want to remove the polylines from the map to hide them, then recreating them would not perform well.
If needed, the polylines could be drawn using D3 on top of the leaflet map but I was hoping it could all be done in leaflet.
Thanks!
JS:
'use strict';
document.addEventListener("DOMContentLoaded", function(event) {
var map = L.map('map').setView([30.5, -0.09], 2);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var pane1 = map.createPane('pane1');
var pane2 = map.createPane('pane2');
function hide(pane) {
pane.style.display = 'none';
}
function show(pane) {
pane.style.display = '';
}
let icon = new L.Icon.Default();
function dr(pane, latlng){
L.polyline([[45.421, -75.697], latlng], {
weight: 2,
pane: pane
}).addTo(map);
L.marker(latlng, {
icon: icon,
pane: pane
}).addTo(map);
}
// define two locations with lines from Ottawa
dr(pane1, [ 42.3732216, -72.5198537 ]);
dr(pane2, [ 35.9606384, -83.9207392 ]);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function loop(){
for( var i = 0; i<100; i++){
hide(pane1);
show(pane2);
await sleep(1000);
hide(pane2);
show(pane1);
await sleep(1000);
}
}
loop();});
HTML:
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.6.0/leaflet-src.js" integrity="sha256-wc8X54qvAHZGQY6nFv24TKlc3wtO0bgAfqj8AutTIu0=" crossorigin="anonymous"></script>
<style>
html, body {
height: 100%;
margin: 0;
}
#map {
width: 900px;
height: 500px;
}
</style>
</head>
<body>
<div id='map'></div>
</body>
</html>
<script src="c.js"></script>

How to get marker popup on hover element from list using Leaflet and L.Control.ListMarkers?

Using Leaflet and plugins like ListMarkers, I display a list of visible markers on the map. After hovering over the appropriate <li> element in the List, I want the marker popup to be displayed. The <li> element contains various information about the product, unfortunately after hovering over them, the popup appears and disappears. How to make it that if I hover entire <li> element popup will show and not go crazy of each element separately ?
var list = new L.Control.ListMarkers({ layer: markers, itemIcon: null });
list.addTo(map);
list.on('item-mouseover', function(e) {
e.layer.setIcon(L.icon({
iconUrl: '/marker2.png'
}));
e.layer.openPopup();
}).on('item-mouseout', function(e) {
e.layer.setIcon(L.icon({
iconUrl: '/marker.png'
}))
e.layer.closePopup();
});
listaHTML = list.getContainer();
document.getElementById("lista").appendChild(listaHTML);
Seems to be a CSS problem.
Workaround
li.list-markers-li>a>span,
li.list-markers-li>a>b {
pointer-events: none;
}
var map = new L.Map('map', {
zoom: 10,
minZoom: 10,
center: L.latLng(43.90974, 10.2419)
});
map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')); //base layer
var markersLayer = new L.LayerGroup(); //layer contain searched elements
map.addLayer(markersLayer);
////////////populate map from cities-italy.js
for (var i in cities) {
let marker = L.marker(L.latLng(cities[i].loc), {
title: cities[i].name
}).addTo(markersLayer);
marker.bindPopup(cities[i].name)
}
var list = new L.Control.ListMarkers({
layer: markersLayer,
itemIcon: null
});
list.on('item-mouseover', function(e) {
e.layer.openPopup();
}).on('item-mouseout', function(e) {
e.layer.closePopup();
});
map.addControl(list);
#map {
position: absolute;
top: 35px;
left: 0;
width: 100%;
height: 80%
}
li.list-markers-li>a>span,
li.list-markers-li>a>b {
pointer-events: none;
}
<script src="https://labs.easyblog.it/maps/leaflet-list-markers/examples/cities-italy.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet.js"></script>
<script src="https://labs.easyblog.it/maps/leaflet-list-markers/src/leaflet-list-markers.js"></script>
<link href="https://labs.easyblog.it/maps/leaflet-list-markers/src/leaflet-list-markers.css" rel="stylesheet" />
<div id="map"></div>

ArcGis layers on mapbox gl

I'm trying to add a layer from an api on ArcGis:
https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Facility_and_Structure/MapServer/1
In leaflet it is posible with:
L.esri.featureLayer({
url:"https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Facility_and_Structure/MapServer/1",
style: function () {
return { color: "#70ca49", weight: 2 };
}
}).addTo(map);
Is there a way to do this on mapboxgl?
Hi Jorge Monroy - Mapbox GL JS expects the data sources as such. In your case where you're wanting to load building footprints from an ArcGIS REST Service, your best bet is to load them as a geojson.
It looks like you're publishing the services from ArcServer 10.31. In that case, the way that I've loaded ArcGIS REST services is by exposing them through AGOL as explained there. If you have that option, that seems easiest. Otherwise, there are other (work-arounds)[https://gis.stackexchange.com/questions/13029/converting-arcgis-server-json-to-geojson] that I've no experience with.
Using Washington D.C. as an example, if you navigate to: http://opendata.dc.gov/datasets/building-footprints and then click on APIs, you can copy the link to the geojson service.
You can then load into MapboxGL JS through the data property of the geojson source shown there.
You can use leaflet-mapbox-gl.js to integrate leaflet and mapbox. Get token from mapbox and add it to below example to make it work.
References: https://github.com/mapbox/mapbox-gl-leaflet
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css' rel='stylesheet' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js'></script>
<script src="https://unpkg.com/mapbox-gl-leaflet/leaflet-mapbox-gl.js"></script>
<script src="https://unpkg.com/esri-leaflet/dist/esri-leaflet.js"></script>
<style>
html, body, #map {
margin:0; padding:0; width : 100%; height : 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
var token = "";//add token before running this example
const INITIAL_VIEW_STATE = {
latitude: 45.528,
longitude: -122.680,
zoom: 13
};
var map = L.map('map').setView([45.528, -122.680], 13);
L.esri.basemapLayer("NationalGeographic").addTo(map);
var parks = L.esri.featureLayer({
url: "https://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/Portland_Parks/FeatureServer/0",
style: function () {
return { color: "#70ca49", weight: 2 };
}
}).addTo(map);
var gl = L.mapboxGL({
accessToken: token,
style: 'mapbox://styles/mapbox/dark-v10'
}).addTo(map);
//To add anything on mapbox, first access the mapbox using getMapboxMap()
gl.getMapboxMap().on('load', () => {
//To load any layer on mapbox
//gl.getMapboxMap().addLayer();
});
var popupTemplate = "<h3>{NAME}</h3>{ACRES} Acres<br><small>Property ID: {PROPERTYID}<small>";
parks.bindPopup(function(e){
return L.Util.template(popupTemplate, e.feature.properties)
});
</script>
</body>
</html>

How to implement autocomplete while using locator in arcgis

The below code is to find a location on map, once the location is entered in a textbox.Please note in the below code that I am using 'locator' instead of 'geocoder' as i would like to have custom textbox instead of the textbox provided by the 'esri/dijit/geocoder' and also i would like to get the geocoordinates values using locator.
In the below code, i would like to add 'autocomplete' feature in textbox that has the same functionality as of 'autocomplete' feature in 'geocoder'.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<!--The viewport meta tag is used to improve the presentation and behavior of the samples
on iOS devices-->
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>Find Address</title>
<link rel="stylesheet" href="http://js.arcgis.com/3.12/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.12/esri/css/esri.css">
<style>
html, body {
height: 100%; width: 100%;
margin: 0; padding: 0;
}
#map{
padding:0;
border:solid 1px #343642;
margin:5px 5px 5px 0px;
}
#leftPane{
width:20%;
border-top: solid 1px #343642;
border-left: solid 1px #343642;
border-bottom: solid 1px #343642;
margin:5px 0px 5px 5px;
color: #343642;
font:100% Georgia,"Times New Roman",Times,serif;
/*letter-spacing: 0.05em;*/
}
</style>
<script src="http://js.arcgis.com/3.12/"></script>
<script>
var map, locator;
require([
"esri/map", "esri/tasks/locator", "esri/graphic",
"esri/InfoTemplate", "esri/symbols/SimpleMarkerSymbol",
"esri/symbols/Font", "esri/symbols/TextSymbol",
"dojo/_base/array", "esri/Color",
"dojo/number", "dojo/parser", "dojo/dom", "dijit/registry",
"dijit/form/Button", "dijit/form/Textarea",
"dijit/layout/BorderContainer", "dijit/layout/ContentPane", "dojo/domReady!"
], function(
Map, Locator, Graphic,
InfoTemplate, SimpleMarkerSymbol,
Font, TextSymbol,
arrayUtils, Color,
number, parser, dom, registry
) {
parser.parse();
map = new Map("map", {
basemap: "streets",
center: [-93.5, 41.431],
zoom: 5
});
locator = new Locator("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
locator.on("address-to-locations-complete", showResults);
// listen for button click then geocode
registry.byId("locate").on("click", locate);
map.infoWindow.resize(200,125);
function locate() {
map.graphics.clear();
var address = {
"SingleLine": dom.byId("address").value
};
locator.outSpatialReference = map.spatialReference;
var options = {
address: address,
outFields: ["Loc_name"]
};
locator.addressToLocations(options);
}
function showResults(evt) {
var symbol = new SimpleMarkerSymbol();
var infoTemplate = new InfoTemplate(
"Location",
"Address: ${address}<br />Score: ${score}<br />Source locator: ${locatorName}"
);
symbol.setStyle(SimpleMarkerSymbol.STYLE_SQUARE);
symbol.setColor(new Color([153,0,51,0.75]));
var geom;
arrayUtils.every(evt.addresses, function(candidate) {
console.log(candidate.score);
if (candidate.score > 80) {
console.log(candidate.location);
var attributes = {
address: candidate.address,
score: candidate.score,
locatorName: candidate.attributes.Loc_name
};
geom = candidate.location;
var graphic = new Graphic(geom, symbol, attributes, infoTemplate);
//add a graphic to the map at the geocoded location
map.graphics.add(graphic);
//add a text symbol to the map listing the location of the matched address.
var displayText = candidate.address;
var font = new Font(
"16pt",
Font.STYLE_NORMAL,
Font.VARIANT_NORMAL,
Font.WEIGHT_BOLD,
"Helvetica"
);
var textSymbol = new TextSymbol(
displayText,
font,
new Color("#666633")
);
textSymbol.setOffset(0,8);
map.graphics.add(new Graphic(geom, textSymbol));
return false; //break out of loop after one candidate with score greater than 80 is found.
}
});
if ( geom !== undefined ) {
map.centerAndZoom(geom, 12);
}
}
});
</script>
</head>
<body class="claro">
<div id="mainWindow" data-dojo-type="dijit/layout/BorderContainer"
data-dojo-props="design:'sidebar', gutters:false"
style="width:100%; height:100%;">
<div id="leftPane"
data-dojo-type="dijit/layout/ContentPane"
data-dojo-props="region:'left'">
Enter an address then click the locate button to use a sample address locator to return the location for
street addresses in the United States.
<br>
<textarea id="address">380 New York St, Redlands</textArea>
<br>
<button id="locate" data-dojo-type="dijit/form/Button">Locate</button>
</div>
<div id="map"
data-dojo-type="dijit/layout/ContentPane"
data-dojo-props="region:'center'">
</div>
</div>
</body>
</html>
How to add 'autocomplete' feature in this code?
What do you mean by using your own custom textbox provided by esri/dijit/geocoder? The Geocoder dijit comes with default ESRI css to style the dijit, but nothing prevents you from overriding this with your own styles.
For instance you could add a class in your body tag to override the claro styles:
<body class="claro custom">
This way esri dijits will use claro by default, but if you define the same css selectors as the esri dijit and append them with your custom class, the dijit will use that instead. Here's a short example where we override 2 properties of the results element in the Geocoder:
/* Custom styles for the Geocoder dijit */
.custom #myGeocoder .esriGeocoderResults {
overflow: visible;
z-index: 1000 !important;
}
The geocode dijit supports autocomplete using either the default locator or custom ones. Since you are referencing the default locator service you can just use the dijit and pass autoComplete in the options
<script>
var map, geocoder;
require([
"esri/map", "esri/dijit/Geocoder", "dojo/domReady!"
], function(Map, Geocoder) {
map = new Map("map",{
basemap: "gray",
center: [-120.435, 46.159], // lon, lat
zoom: 7
});
geocoder = new Geocoder({
map: map,
autoComplete : true
}, "search");
geocoder.startup();
});
</script>