How to used deckgl in "Bing Map" - bing-maps

We succeeded in displaying nearly 10 million pieces of data on a map using the Google Map API and Deck GL library.
However, I don't like the price and license policy of the Google Map API, so I'm going to change it to Bing Map.
It is difficult to find examples or examples of using Bing Map and Deckgl together on the Internet.
I understand that Deckgl can be used interworking with any base map if it meets a specific condition, but I'm not sure what the specific condition is.
What I want to know is as follows.
Can I use it with deckgl?
Which map do you prefer if you use Deckgl between OSM and BingMap?
Can you handle the map on BingMap? (getCenter, setCenter, etc.)
Is there a Map Event? (Clicked Event, Dragged Event, etc.)
Is 3D building or setTilt possible like MapBox?

There is no example for this currently, but this could be achieved by adding a canvas to the map as a custom layer. Here is an example: https://bingmapsv8samples.azurewebsites.net/#Canvas%20Layer
Alternatively, consider using Azure Maps. The Azure Maps web SDK wraps MapLibre (open source community fork of Mapbox), Deck.gl was originally writen for Mapbox, so it's not too difficult to access the underlying API in the Azure Maps web SDK and get deck.gl working. I've experimented with this a bit in the past. Here is a quick example:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.js"></script>
<script src="https://unpkg.com/deck.gl#8.4.9/dist.min.js"></script>
<script type='text/javascript'>
//https://blog.mapbox.com/coloring-lidar-4522ca5a7186
var map;
var arcData, currentStyle;
function GetMap() {
//Initialize a map instance.
map = new atlas.Map('myMap', {
center: [-85, 35],
zoom: 5,
pitch: 30,
view: 'Auto',
style:'grayscale_dark',
//Add your Azure Maps subscription key to the map SDK. Get an Azure Maps key at https://azure.com/maps
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: '<Your Azure Maps Key>'
}
});
//Wait until the map resources are ready.
map.events.add('ready', function () {
//Load external data.
fetch('https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/arc/counties.json')
.then(response => response.json())
.then(({ features }) => {
//Add deck gl layer to map.
map.layers.add(new AzureMapsLayer({
id: 'arc',
data: calculateArcs(features),
getSourcePosition: d => d.source,
getTargetPosition: d => d.target,
getSourceColor: [255, 0, 0],
getTargetColor: [0, 255, 0],
getWidth: 2,
type: deck.ArcLayer
}));
});
});
}
function calculateArcs(data, selectedCounty) {
if (!data || !data.length) {
return null;
}
if (!selectedCounty) {
selectedCounty = data.find(f => f.properties.name === 'New York, NY');
}
const { flows, centroid } = selectedCounty.properties;
const arcs = Object.keys(flows).map(toId => {
const f = data[toId];
return {
source: centroid,
target: f.properties.centroid,
value: flows[toId]
};
});
const scale = Math.random();
arcs.forEach(a => {
a.gain = Math.sign(a.value);
a.quantile = scale;
});
return arcs;
}
class AzureMapsLayer extends atlas.layer.Layer {
constructor(options) {
super(options.id);
this._mbLayer = new deck.MapboxLayer(options);
this.source = new atlas.source.DataSource();
}
/**
* Internal method for building the mapbox layers.
* Because this layer only wraps others this is always empty.
* #internal
*/
_buildLayers() {
return [this._mbLayer];
}
/**
* Internal method for getting the ids of the mapbox layers this layer produces.
* Because this layer wraps others we return their ids.
* #internal
*/
_getLayerIds() {
return [this.id];
}
_getSource() {
return this._mbLayer.source;
}
/**
* #internal
*/
_getSourceIds() {
var ids = new Set();
ids.add(this.source.getId());
return ids;
}
}
</script>
<style>
html, body, #myMap {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
</style>
</head>
<body onload="GetMap()">
<div id="myMap"></div>
</body>
</html>
Here is what the above code generates:

Related

Bing Maps Route Optimization

I am attempting to generate an optimized route using Bing Maps re this article, https://learn.microsoft.com/en-us/bingmaps/rest-services/examples/optimized-waypoints-example#example, but am struggling to know how to render the resultant route on screen. For normal routes I am using this routine which seems to work well enough...
function traceRoute(){
infobox.setOptions({visible:false});
if(Object.entries(fromCoords).length !== 0 && Object.entries(toCoords).length !== 0){
Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function () {
var directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
directionsManager.setRequestOptions({ routeMode: Microsoft.Maps.Directions.RouteMode.driving });
var waypoint1 = new Microsoft.Maps.Directions.Waypoint({ address: fromCoords.title, location: new Microsoft.Maps.Location(fromCoords.lat,fromCoords.long) });
var waypoint2 = new Microsoft.Maps.Directions.Waypoint({ address: toCoords.title, location: new Microsoft.Maps.Location(toCoords.lat,toCoords.long) });
directionsManager.addWaypoint(waypoint1);
directionsManager.addWaypoint(waypoint2);
// Set the element in which the itinerary will be rendered
directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('printoutPanel') });
directionsManager.calculateDirections();
});
}
}
However, I cannot figure out how to do they same with an optimised API call. Is it possible to add a list of way points to the directionsManager and set an Optimised flag?
Digging through the documentation and trying to reverse engineer the directions module/manager, there doesn't appear to be any support for this option in the module.
One solution would be to call the REST service directly to get the optimized ordering of the waypoints, then pass those ordered waypoints into the directions manager. This will allow you to leverage the rendering capabilities of the directions module which will make development a lot easier.
Here is a code sample:
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<script type='text/javascript' src='https://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=<Your Bing Maps Key>'></script>
<script>
var map, directionsManager;
//Get optimized waypoint orders based on time with traffic, and only get minimial information back.
var directionsUrl = 'http://dev.virtualearth.net/REST/V1/Routes/Driving?&optimizeWaypoints=true&optimize=timeWithTraffic&routeAttributes=excludeItinerary&key={key}';
function GetMap()
{
map = new Microsoft.Maps.Map('#myMap', {});
//Load the directions module.
Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function () {
//Create an instance of the directions manager.
directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
directionsManager.setRequestOptions({ routeOptimization: 'timeWithTraffic' });
//Pass in waypoints to calculate optimized order.
calculateOptimizedOrder([
new Microsoft.Maps.Directions.Waypoint({ address: '86 Pike Pl, Seattle, WA 98101' }),
new Microsoft.Maps.Directions.Waypoint({ address: 'Troll Ave N, Seattle, WA 98103' }),
new Microsoft.Maps.Directions.Waypoint({ address: '3800 Montlake Blvd NE, Seattle, WA 98195' }),
new Microsoft.Maps.Directions.Waypoint({ address: '1000 4th Ave, Seattle, WA 98104' }),
new Microsoft.Maps.Directions.Waypoint({ address: '5400 Ballard Ave NW, Seattle, WA 98107' })
]);
});
}
function calculateOptimizedOrder(waypoints) {
map.getCredentials((sessionId) => {
//Add the key to the REST request.
var request = [directionsUrl.replace('{key}', sessionId)];
//Add the waypoints to the REST request.
for(var i=0;i<waypoints.length;i++){
var wp = waypoints[i];
request.push('&wp.', i, '=');
var loc = wp.getLocation();
var add = wp.getAddress();
if(loc) {
request.push(loc.toString());
} else if(add){
request.push(encodeURIComponent(add));
} else {
throw 'No waypoint info provided';
}
}
//Combine the request parts to create the URL.
var url = request.join('');
//Process the request.
fetch(url).then(r => r.json()).then(r => {
var orderedWaypoints = [];
var waypointsOrder = r.resourceSets[0].resources[0].waypointsOrder;
for(var i=0;i<waypointsOrder.length;i++){
//Extract the number from the waypoint order text.
var idx = parseInt(waypointsOrder[i].replace('wp.',''));
//Cross reference the original waypoints.
orderedWaypoints.push(waypoints[idx]);
}
//Calculate and render directions using the optimized order.
calculateDirections(orderedWaypoints);
alert('Optimized order: ' + waypointsOrder.join(','));
});
});
}
function calculateDirections(orderedWaypoints) {
for(var i=0;i< orderedWaypoints.length;i++){
directionsManager.addWaypoint(orderedWaypoints[i]);
}
//Calculate directions.
directionsManager.calculateDirections();
}
</script>
</head>
<body>
<div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>
</body>
</html>

ArcGIS JavaScript API Popup Not Referencing REST Service Layer

The content in the popup created through the variable "popupCustom" is displaying string instead of referencing the specified field {IN_COUNTRY}. I followed the ArcGIS JS API Popup Tutorials, & can't see what my error is in failing to grab the attributes associated with that field. Here's the code -- any help is greatly appreciated!
*note: feature layer url within "Cyber_Areas" variable points to REST URL for referenced Feature Class.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Search widget with multiple sources - 4.6</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.6/esri/css/main.css">
<script src="https://js.arcgis.com/4.6/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/BasemapToggle",
"esri/widgets/Legend",
"esri/layers/TileLayer",
"esri/layers/FeatureLayer",
"esri/widgets/Search",
"esri/widgets/LayerList",
"esri/PopupTemplate",
"dojo/on",
"dojo/domReady!"
], function(
Map,
MapView,
BasemapToggle,
Legend,
TileLayer,
FeatureLayer,
Search,
LayerList,
PopupTemplate,
on
) {
var Cyber_Areas = new FeatureLayer({
url: "*inserturl*",
outFields: ["IN_COUNTRY"],
popupTemplate: popupCustom
});
var map = new Map({
basemap: "osm"
});
map.add(Cyber_Areas);
var view = new MapView({
container: "viewDiv",
map: map,
center: [-87.172865, 34.077613], // lon, lat
zoom: 16
});
var searchWidget = new Search({
view: view,
popupOpenOnSelect: false
});
view.ui.add(searchWidget, {
position: "top-left",
index: 0
});
var popupCustom = searchWidget.on('select-result', function(evt){
//console.info(evt);
view.popup.open({
location: evt.result.feature.geometry, // location of the click on the view
title: "Service Availability:", // title displayed in the popup
content: "<p><b>{IN_COUNTRY}"
});
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
From your code you are mixing the popup template value with when to display it. And those are two different things.
First, you are not setting correctly the popup template of the layer. It should be a PopupTemplate.
It seems to me that in you code the layer definition should be something like this,
var Cyber_Areas = new FeatureLayer({
url: "*inserturl*",
popupTemplate: {
outFields: ["IN_COUNTRY"],
title: "Service Availability:",
content: "<p><b>{IN_COUNTRY}</b></p>"
}
});
Now if you don't want the default behavior of the popup (left click on a feature), you cant disable it like this,
view.popup.autoOpenEnabled = false; // <- disable view popup auto open
And then you can open it wherever you want like this,
view.popup.open({ // <- open popup
location: evt.result.feature.geometry, // <- use map point of the event result
fetchFeatures: true // <- fetch the selected features (if any)
});
You have to understand that the fields you use in the content of the popup template are related to the layer. That is why i set in the popup of the view to fetch the results.

Mapbox GL NavigationControl Events

I have an instance of a Mapbox GL map, after load of my data source I am calling fitBounds() to change the map's center and zoom to fit my data set. I've also attached a number of event listeners to this map because I want to know when the user manually changed the map's zoom or position.
Mapbox also triggers 'movestart' and 'zoomstart' on fitBounds(), though I'm getting around that problem by checking for the presence of the originalEvent property in the event callback.
The problem is, I also have a NavigationControl added to the map, and user interactions triggered through its zoom or rotate buttons fire my map events without the originalEvent property. I cannot find any way in the Mapbox documentation to listen attach event listeners to the NavigationControl, nor a way to differentiate between a zoom / pan initiated by a fitBounds call vs. a user interaction through that component.
Is there something I'm missing? Is there a way to attach mouse / touch event listeners to the NavigationControl component? Or perhaps is there some property within the event objects that will tell me the source of the event?
Simplified code sample -
this._userMoved = false;
this._map = new mapboxgl.Map(options);
// listen for user actions that update the map display
['movestart', 'zoomstart', 'boxzoomstart', 'rotatestart', 'pitchstart'].forEach((action) => {
this._map.on(action, (e) => {
if (e.originalEvent) {
// if this property is set, the event in question was triggered by an actual user ineraction.
// EXCEPT when the user interaction came from the NavigationControl, hence the problem
this._userMoved = true;
}
});
});
this._map.on('load', () => {
// add the control after map load
this._map.addControl(new mapboxgl.NavigationControl(),'top-left');
this._setMapData(); // adds my data source to the map
this._setMapView(); // calls this._map.fitBounds() using my data source
});
If your need is specifically to handle a specific event (fitbounds) that is being called once, then you can do this:
this._map.once('moveend', e => {
// do whatever you do after the fitbounds event.
this._map.on(['movestart', 'zoomstart', 'boxzoomstart', 'rotatestart', 'pitchstart'], userMovementHandler)
});
EDIT
I just looked more closely at the documentation and there is indeed an eventData parameter to fitBounds which is intended to solve exactly this problem.
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Display a map</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.43.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.43.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 = 'pk.eyJ1Ijoic3RldmFnZSIsImEiOiJGcW03aExzIn0.QUkUmTGIO3gGt83HiRIjQw';
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/streets-v9', // stylesheet location
center: [-74.50, 40], // starting position [lng, lat]
zoom: 9 // starting zoom
}).on('moveend', e => {
if (e.source === 'fitBounds') {
console.log('Caused by fitBounds');
} else {
console.log('Caused by user');
}
})
map.fitBounds([140,-42, 150,-37], {}, {source: 'fitBounds'})
</script>
</body>
</html>

Leaflet JS + Leaflet.Deflate - Changing default marker icon to custom icon

In my previous post 'Leaflet JS - changing esri shape into marker on certain zoom level
' I was able to resolve an issue which i had with the leaflet JS library and changing the polygon shapes to markers icons when hitting a certain zoom level.
I was advised by 'Ivan Sanchez' to use the 'Leaflet.Deflate' plugin and this works like a charm, so now the various shapes are being transformed into markers after a certain zoomlevel, however now I'm struggling to change the default leaflet marker icon to a custom marker icon, so my question now is:
Is it possible to change the default marker icon to a custom marker icon while using the 'Leaflet.ShapeFile' and 'Leaflet.Deflate' plugin and what would be the best approach to do this?
I wanted to make a JSFiddle, but I don't JSFiddle allows me to attach the zip file contains the shapefiles, so I will post the code I have got so far below here, thanks for your help, advise and support:
<!doctype html>
<html lang="en">
<head>
<meta charset='utf-8' />
<title>v4</title>
<link rel="stylesheet" type="text/css" href="lib/leaflet/leaflet.css" />
<!--[if lte IE 8]> <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.ie.css" /> <![endif]-->
<link rel="stylesheet" type="text/css" href="lib/leaflet/L.Control.Sidebar.css" />
<style>
html { height: 100% }
body { height: 100%; margin: 0; padding: 0; }
#map { height: 100% }
</style>
</head>
<body>
<div id="map"></div>
<script src="lib/jquery/jquery-3.1.1.min.js"></script>
<script src="lib/leaflet/leaflet.js"></script>
<script src="lib/leaflet/catiline.js"></script>
<script src="lib/leaflet/leaflet.shpfile.js"></script>
<script src="lib/leaflet/shp.js"></script>
<script src="lib/leaflet/L.Control.Sidebar.js"></script>
<script src="lib/leaflet/L.Deflate.js"></script>
<script>
// init map
var m = L.map('map').setView([52.472833, 1.749609], 15);
// clicking on the map will hide the sidebar plugin.
m.on('click', function () {
sidebar.hide();
});
// init Deflate plugin
L.Deflate({ minSize: 10 }).addTo(m);
// Init side bar control
var sidebar = L.control.sidebar('sidebar', { closeButton: true, position: 'right' });
m.addControl(sidebar);
// Init esri shape file via leaflet.shapefile, shp.js plugin
var businessProperties = new L.Shapefile('data/businessshapes.zip', { style: propertyStyle, onEachFeature: propertyOnEachFeature }).addTo(m);
// create on-click Feature
function propertyOnEachFeature(feature, layer) {
layer.on( {
mouseover: highlightFeature,
mouseout: resetHighlight,
click: function populate() {
sidebar.toggle();
document.getElementById('pinfoHeader').innerHTML = "<h2>" + feature.properties.Building + " - Detailed Information</h2><br />";
document.getElementById('pTitle').innerHTML = "Name: " + feature.properties.Building
document.getElementById('pDetails').innerHTML = "SHAPE_Leng: " + feature.properties.SHAPE_Leng + "<br/ >SHAPE_Area: " + feature.properties.SHAPE_Area
}, highlightFeature, zoomToFeature
});
}
// style the properties style
function propertyStyle(feature) {
return {
fillColor: getPropertyColor(feature.properties.BusType),
weight: 2,
opacity: 1,
color: 'white',
dashArray: 3,
fillOpacity: 0.7
}
}
// set color per property according to the data table of the Esri Shape file.
function getPropertyColor(propStatus) {
if (propStatus == 'TypeA') {
return 'red';
} else if (propStatus == 'TypeB') {
return 'green';
} else {
return 'yellow';
}
}
// set the highlighted color for polygon
function highlightFeature(e) {
var layer = e.target;
layer.setStyle( {
weight: 2,
color: 'black',
fillColor: 'white',
fillOpacity: 0.2
});
if (!L.Browser.ie && !L.Browser.opera) {
layer.bringToFront();
}
}
// reset the highlighted color for polygon after mouse leave polygon
function resetHighlight(e) {
businessProperties.resetStyle(e.target);
}
//Extend the Default marker class to overwrite the leaflet.deflate marker icon???
var TestIcon = L.Icon.Default.extend({
options: {
iconUrl: 'assets/images/markers/business.png'
}
});
var testIcon = new TestIcon();
businessProperties.addTo(m);
// Init base maps for switch
var grayscale = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', { id: 'MapID', attribution: 'Map maintained by Demo LTD, — Map data © OpenStreetMap,' }).addTo(m);
var streets = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { id: 'MapID', attribution: 'Map maintained by Demo LTD, — Map data © OpenStreetMap,' });
var baseMaps = {
"Streets": streets,
"Grayscale": grayscale
};
// Init overlay map switch
var overlayMaps = {
"Bussines Properties": businessProperties
};
// Add switches to map control
L.control.layers(baseMaps, overlayMaps).addTo(m);
</script>
</body>
</html>
Is it possible to change the default marker icon to a custom marker icon while using the 'Leaflet.Deflate' plugin?
The answer is: No.
The current code for Leaflet.Deflate uses a default marker and a default marker only, see https://github.com/oliverroick/Leaflet.Deflate/blob/991f51ca82e7bb14a17c8d769b4c378c4ebaf700/src/L.Deflate.js#L42
You could hack your way around it, but I would rather recommend filing a feature request in the Leaflet.Deflate repo. It should be possible to modify the Leaflet.Deflate repo to allow line/polygon features to have some extra properties to be used as marker options.

Multiple KML w/ Google Earth Plugin/API Limitation?

So, I'm building a web application using the Google Earth Plugin and API. However, I'm running into an issue with trying to display more than one KML file; only the last file given loads up. I'm using the process KmlNetworkLink to display everything.
The desktop application allows this, so I'm not sure if this is just a limitation on the API or not. Does anyone know if this is a limitation?
Thanks in advance.
The documentation I am looking at:
https://developers.google.com/earth/documentation/kml
EDIT: OP here. After working on this project for a few weeks, I've learned how to properly set up multiple KML tracks with the Google Earth plugin. I've revised my earlier answer (now shown below) to include a much cleaner and abstracted version of code. Hope this helps someone. Also (not listed here), it is possible store all of your KML locations in a single JSON and loop through it while calling createView() if needed.
<!DOCTYPE html>
<html>
<head>
<title>Google Earth API Display</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
#earthDisplay {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="earthDisplay"></div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
/**
* ==================================================================
* Global Variables and Statements
* ==================================================================
*/
var ge;
google.load("earth", "1", {"other_params": "false"});
google.setOnLoadCallback(init);
/**
* ==================================================================
* Functions
* ==================================================================
*/
function init()
{
google.earth.createInstance('earthDisplay', initCB, failureCB);
}
function initCB(instance)
{
var kmlLocation = "insert/your/file/here.kml";
ge = instance;
ge.getWindow().setVisibility(true);
createView(kmlLocation); // This function can be called multiple times to load different views.
}
function createView(kmlLocation)
{
var href = kmlLocation;
var link = ge.createLink('');
var networkLink = ge.createNetworkLink('');
link.setHref(href);
networkLink.set(link, true, true); // Sets the link, refreshVisibility, and flyToView
ge.getWindow().setVisibility(true);
ge.getFeatures().appendChild(networkLink);
}
function failureCB(errorCode)
{
alert("There has been an error with the Google Earth API. Please check your console.");
console.log("Error with Google Earth API: " + errorCode);
}
</script>
</body>
</html>