Mapbox Custom Marker Rotation - html

After having followed a mapbox tutorial, I managed to display a drone on a map.
My only question is :
How can I add a rotation parameter in my code (to display the drone-marker on different angles) ?
I have spent hours looking for examples but none corresponds to what I already wrote...
Thank you !
Here's the script :
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<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%;
}
.marker {
background-image: url('MQ-1_Predator_silhouette.svg.png');
background-size: cover;
width: 61px;
height: 35px;
border-radius: 50%;
cursor: pointer;
}
</style>
</head>
<body>
<div id='map'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoibWF0dGhpZXU2MyIsImEiOiJjamNob3I3cmgxam1kMzFzNzdja2ZvNmhuIn0.AyFos9o0afaaBU21CgrxXg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v9',
center: [-96, 37.8],
zoom: 3
});
// code from the next step will go here!
var geojson = {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-90,40]
},
properties: {
title: 'Mapbox'
}
}]
};
// add markers to map
geojson.features.forEach(function(marker) {
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.addTo(map);
});
</script>
</body>
</html>

Mapbox offers inbuilt icon rotation feature under the heading of get bearing and icon rotation is handled internally once you find out geographical bearing between two pairs of LatLng and feed that value to get Bearing.
If you are using mapbox marker and are keen on rotating it, you can use css transform property (rotate()) and dynamically calculate the angle between two pairs of latLng and use that value in rotate property.
var dLon = destination[0]-origin[0];
var dLat = destination[1]-origin[1];
var angle = 180+(Math.atan2(dLon, dLat) * 180 / Math.PI);
var rotateString = "rotate(" + angle + "deg)";
var el = document.createElement('div');
el.className = 'marker';
var truckMarker = new mapboxgl.Marker(el)
el.style.transform = el.style.transform + rotateString;
In the last line it is important to append the rotate property because the transform property is already being updated as translate is called because of animation.
Works perfectly fine for me!!

If you're using Markers then you'll need to do the rotation yourself as part of the marker element style. This would probably only work if you disable map rotation, or unless you do something like https://github.com/mapbox/mapbox-gl-js/issues/3937#issuecomment-304916394 to account for the map rotation yourself.
If you're using Symbols then it's much easier as you can use https://www.mapbox.com/mapbox-gl-js/style-spec#layout-symbol-icon-rotate to set your rotation.

Related

how to render offscreen canvas properly

I'm trying to create a basic example of offscreen rendering canvas but I'm error in js "cannot read property of context". actually my idea is to create a demo like I saw in https://yalantis.com/ I want to create my name initial. If there is any better idea to achieve this then please enlighten me.
Thanks here is my basic attempt before the actual implementation :)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Off Screen Canvas</title>
<script>
function createOffscreenCanvas() {
var offScreenCanvas= document.createElement('canvas');
offScreenCanvas.width= '1360px';
offScreenCanvas.height= '400px';
var context= offScreenCanvas.getContext("2d");
context.fillRect(10,10,200,200);
}
function copyToOnScreen(offScreenCanvas) {
var onScreenContext=document.getElementById('onScreen').getContext('2d');
var offScreenContext=offScreenCanvas.getContext('2d');
var image=offScreenCanvas.getImageData(10,10,200,200);
onScreenContext.putImageData(image,0,0);
}
function main() {
copyToOnScreen(createOffscreenCanvas());
}
</script>
<style>
#onScreen {
width:1360px;
height: 400px;
}
</style>
</head>
<body onload="main()">
<canvas id="onScreen"></canvas>
</body>
</html>
You could achieve this in the following way ...
function createOffscreenCanvas() {
var offScreenCanvas = document.createElement('canvas');
offScreenCanvas.width = '1360';
offScreenCanvas.height = '400';
var context = offScreenCanvas.getContext("2d");
context.fillStyle = 'orange'; //set fill color
context.fillRect(10, 10, 200, 200);
return offScreenCanvas; //return canvas element
}
function copyToOnScreen(offScreenCanvas) {
var onScreenContext = document.getElementById('onScreen').getContext('2d');
onScreenContext.drawImage(offScreenCanvas, 0, 0);
}
function main() {
copyToOnScreen(createOffscreenCanvas());
}
canvas {
border: 1px solid black;
}
<body onload="main()">
<canvas id="onScreen" width="1360" height="400"></canvas>
note : never set canvas's width and height using css. instead use the native width and height property of the canvas.
You can try to do that with experimental OffScreenCanvas API. I'm leaving the link here.
So you don't actually need a canvas element attached to DOM with this experimental API, furthermore you can perform your drawing in webworkers, so it will not block the browser's main thread if you're drawing thousands of objects.
const offscreen = new OffscreenCanvas(256, 256);
offscreen.getContext("2d") // or webgl, etc.
Please beware that it's experimental. You can try to check like "OffscreenCanvas" in window and use that, if it's not available, you can fallback to document.createElement("canvas").
Return offScreenCanvas in your function createOffscreenCanvas
function createOffscreenCanvas() {
var offScreenCanvas= document.createElement('canvas');
offScreenCanvas.width= '1360px';
offScreenCanvas.height= '400px';
var context= offScreenCanvas.getContext("2d");
context.fillRect(10,10,200,200);
return offScreenCanvas;
}
Edit
You were getting image date from canvas not context.
function copyToOnScreen(offScreenCanvas) {
var onScreenContext=document.getElementById('onScreen').getContext('2d');
var offScreenContext = offScreenCanvas.getContext('2d');
var image=offScreenContext.getImageData(10,10,200,200);
onScreenContext.putImageData(image,0,0);
}

Get current zoom in Cesium

I need to create a setZoom() function in Cesium. For that, I believe I need to evaluate the current zoom so I can decide if I have to use the zoomIn or zoomOut to show what the user asks.
Does anyone know if it's possible to get the zoom level from the map when using Cesium? Or any other solution... Any tips are very welcome.
Does the function getMagnitude() does the trick?
Thanks!
Solution:
I put together all the tips emackey gave to me and got the following code:
var iniPos = new Cesium.Cartesian3();
iniPos = this.viewer.camera.position;
var cartographic = new Cesium.Cartographic();
cartographic.height = zoom * 1000;
cartographic.longitude = iniPos.x;
cartographic.latitude = iniPos.y;
var newPos = new Cesium.Cartesian3();
Cesium.Ellipsoid.WGS84.cartographicToCartesian(cartographic, newPos);
this.viewer.camera.setView({
position: newPos
});
With that I'm able to define the height of the camera to a zoom parameter defined by the user.
Cesium's default view is a 3D globe with a perspective view. A typical 2D zoom level number doesn't fully describe the different resolutions that Cesium's camera can see. Take a minute to read my full answer to Determining the Map Scale of the Viewed Globe for a better explanation.
EDIT 1: The camera.getMagnitude function gets the "magnitude of the camera's position" which really means the distance to the center of the Earth. This is probably not what you want, instead you want the altitude of the cartographic position.
EDIT 2: I've added a code snippet here that has buttons on it to set the camera's height to various altitudes. Click "Run code snippet" at the bottom to see this in action, or copy just the JavaScript portion of this into Sandcastle to run it there. Note that this works best when the camera is looking straight down, as it moves the camera to a specific height without altering the lat/lon. If the camera is off-axis, the mouse can "zoom" the camera along the look vector, which alters all three cartographic coordinates (lat, lon, and alt) at the same time. It's a trickier calculation to move the camera to a specific height along that line, I don't have code for that handy and it might not really be what you want anyway. Give this a try, see if it works for you.
var viewer = new Cesium.Viewer('cesiumContainer', {
navigationHelpButton: false,
animation: false,
timeline: false
});
var cartographic = new Cesium.Cartographic();
var cartesian = new Cesium.Cartesian3();
var camera = viewer.scene.camera;
var ellipsoid = viewer.scene.mapProjection.ellipsoid;
var toolbar = document.getElementById('toolbar');
toolbar.innerHTML = '<div id="hud"></div>' +
'<button type="button" class="cesium-button" id="h1km">1km height</button>' +
'<button type="button" class="cesium-button" id="h10km">10km height</button>' +
'<button type="button" class="cesium-button" id="h500km">500km height</button>';
toolbar.setAttribute('style', 'background: rgba(42,42,42,0.9); border-radius: 5px;');
var hud = document.getElementById('hud');
viewer.clock.onTick.addEventListener(function(clock) {
ellipsoid.cartesianToCartographic(camera.positionWC, cartographic);
hud.innerHTML =
'Lon: ' + Cesium.Math.toDegrees(cartographic.longitude).toFixed(3) + ' deg<br/>' +
'Lat: ' + Cesium.Math.toDegrees(cartographic.latitude).toFixed(3) + ' deg<br/>' +
'Alt: ' + (cartographic.height * 0.001).toFixed(1) + ' km';
});
function setHeightKm(heightInKilometers) {
ellipsoid.cartesianToCartographic(camera.position, cartographic);
cartographic.height = heightInKilometers * 1000; // convert to meters
ellipsoid.cartographicToCartesian(cartographic, cartesian);
camera.position = cartesian;
}
document.getElementById('h1km').addEventListener('click', function() {
setHeightKm(1);
}, false);
document.getElementById('h10km').addEventListener('click', function() {
setHeightKm(10);
}, false);
document.getElementById('h500km').addEventListener('click', function() {
setHeightKm(500);
}, false);
html, body, #cesiumContainer {
width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden;
font-family: sans-serif; color: #edffff;
}
#toolbar {
padding: 2px 5px;
position: absolute;
top: 5px;
left: 5px;
}
<link href="http://cesiumjs.org/Cesium/Build/Cesium/Widgets/widgets.css"
rel="stylesheet"/>
<script src="http://cesiumjs.org/Cesium/Build/Cesium/Cesium.js"></script>
<div id="cesiumContainer"></div>
<div id="toolbar"></div>

How to print a specific extent of an ArcGis Map?

I'm trying to print a specific zone on an Arcgis maps with the JS API (not the extend that is displayed).
I didn't find any method or option to do this so I tried to change the extend and then print the map :
var extent = new esri.geometry.Extent(
-620526.0922336339,
5993991.149960931,
108988.90572005256,
6293624.300838808,
myMap.spatialReference
);
myMap.setExtent(extent, true).then(function() {
console.log('setExtend is finished');
var template = new esri.tasks.PrintTemplate();
template.exportOptions = {
width : 500,
height : 500
};
template.format = 'jpg';
template.layout = 'MAP_ONLY';
var params = new esri.tasks.PrintParameters();
params.map = myMap;
params.template = template;
var printTask = new esri.tasks.PrintTask(urlToThePrintServer);
printTask.execute(params);
});
Since setExtent is asynchonous and return a defered I have to use the 'then' method.
I can see the map moving but the defered doesn't seem to works ... (I don't see the console.log()).
is there another way to print a specific extend of a map ?
if not why is the 'then' method never called ?
(I'm using the 3.12 JS API)
Your code looks good to me, though obviously you didn't post all your JavaScript or any of your HTML. Maybe you're not requiring the modules you need. Or maybe your code is trying to run before the map is loaded, though that's unlikely because as you say, the map does move. Or maybe something else is wrong.
I put a full working example at http://jsfiddle.net/06jtccx0/ . Hopefully you can compare that to what you're doing and figure out what is wrong with your code. Here's the same code for your convenience:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
<title>Simple Map</title>
<link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
<style>
html, body, #map {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
body {
background-color: #FFF;
overflow: hidden;
font-family: "Trebuchet MS";
}
</style>
<script src="http://js.arcgis.com/3.13/"></script>
<script>
var myMap;
var urlToThePrintServer = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/PrintingTools/GPServer/Export%20Web%20Map%20Task";
require(["esri/map", "dojo/domReady!"], function(Map) {
myMap = new Map("map", {
basemap: "topo", //For full list of pre-defined basemaps, navigate to http://arcg.is/1JVo6Wd
center: [-122.45, 37.75], // longitude, latitude
zoom: 13
});
myMap.on("load", function(map) {
var extent = new esri.geometry.Extent(
-620526.0922336339,
5993991.149960931,
108988.90572005256,
6293624.300838808,
myMap.spatialReference
);
myMap.setExtent(extent, true).then(function() {
console.log('setExtend is finished');
require([
"esri/tasks/PrintTemplate",
"esri/tasks/PrintParameters",
"esri/tasks/PrintTask"
], function(
PrintTemplate,
PrintParameters,
PrintTask
) {
var template = new PrintTemplate();
template.exportOptions = {
width : 500,
height : 500
};
template.format = 'jpg';
template.layout = 'MAP_ONLY';
var params = new PrintParameters();
params.map = myMap;
params.template = template;
var printTask = new PrintTask(urlToThePrintServer);
printTask.execute(params, function(response) {
console.log("The printed document is at " + response.url);
window.open(response.url);
});
});
});
});
});
</script>
</head>
<body>
<div id="map"></div>
</body>
</html>

updating mvcarray on marker move

I am tryign to figure out MVCArray() in google maps v3. I'm using code written by GeoJason as an example. I attached a click event to the markers to get its LatLng postion. It works well but I need to update the MVCArray on the new postion if a marker is dragged to a new location. This part has me stumped.. Anyone know how to do this or can point me to a good resource which explains using MVCArray's? (besides coode docs, its not designed for newbies.. lol)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
<title>GeoJason - Line Length and Polygon Area with Google Maps API v3 Demo</title>
<meta name="keywords" content="" />
<meta name="description" content="Demo of how to get Line Length and Polygon Area with Google Maps API v3" />
<link rel="stylesheet" type="text/css" href="style/default.css" />
<!-- Script -->
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript" src="js/jquery/jquery-1.4.2.js"></script>
<script type="text/javascript">
var map;
var markerImageDefault = new google.maps.MarkerImage('images/markers/measure-vertex.png',null, null, new google.maps.Point(5,5));
var markerImageHover = new google.maps.MarkerImage('images/markers/measure-vertex-hover.png',null, null, new google.maps.Point(8,8));
var measure = {
ll:new google.maps.MVCArray(),
markers:[],
line:null,
poly:null
};
function Init(){
map = new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 15,
center: new google.maps.LatLng(34.96762, -80.47372),
mapTypeId: google.maps.MapTypeId.ROADMAP,
/* Make the map cursor a crosshair so the user thinks they should click something */
draggableCursor:'crosshair'
});
google.maps.event.addListener(map,'click',function(evt){
measureAdd(evt.latLng);
});
}
function measureAdd(ll){
var marker = new google.maps.Marker({
map:map,
position:ll,
draggable:true,
/* Let the user know they can drag the markers to change shape */
title:'Drag me to change the polygon\'s shape',
icon: markerImageDefault
});
var count = measure.ll.push(ll);
var llIndex = count-1;
if (count>2) /* We've got atleast 3 points, we can measure area */
measureCalc();
/* when dragging stops, and there are more than 2 points in our MVCArray, recalculate length and area measurements */
google.maps.event.addListener(marker,'dragend',function(evt){
if (measure.ll.getLength()>2)
measureCalc();
});
/* when the user 'mouseover's a marker change the image so they know something is different (it's draggable) */
google.maps.event.addListener(marker,'mouseover',function(evt){
marker.setIcon(markerImageHover);
});
google.maps.event.addListener(marker,'mouseout',function(evt){
marker.setIcon(markerImageDefault);
});
// This will allow us to click on the first element to close the polygon
google.maps.event.addListener(marker,'click',function(evt){
//alert(ll + " : " + measure.markers[0].position);
console.log(ll.LatLng);
if(ll == measure.markers[0].position) // Only for the first item
{
alert("You clicked!");
}
});
/* when we drag a marker it resets its respective LatLng value in an MVCArray. Since we're changing a value in an MVCArray, any lines or polygons on the map that reference this MVCArray also change shape ... Perfect! */
google.maps.event.addListener(marker,'drag',function(evt){
measure.ll.setAt(llIndex,evt.latLng);
});
measure.markers.push(marker);
/* find out of the user placed a marker at the end of the polygon. */
if (measure.ll.getLength()>1){
/* We've got 2 points, we can draw a line now */
if (!measure.line){
measure.line = new google.maps.Polyline({
map:map,
clickable:false,
strokeColor:'#FF0000',
strokeOpacity:0.5,
strokeWeight:3,
path:measure.ll
});
}
if (measure.ll.getLength()>2){
/* We've got 3 points, we can draw a polygon now */
if (!measure.poly){
measure.poly = new google.maps.Polygon({
clickable:false,
map:map,
fillOpacity:0.25,
strokeOpacity:0,
paths:measure.ll
});
}
}
}
}
function measureReset(){
/* Remove Polygon */
if (measure.poly) {
measure.poly.setMap(null);
measure.poly = null;
}
/* Remove Line */
if (measure.line) {
measure.line.setMap(null);
measure.line = null;
}
/* remove all LatLngs from the MVCArray */
while (measure.ll.getLength()>0) measure.ll.pop();
/* remove all markers */
for (i=0;i<measure.markers.length;i++){
measure.markers[i].setMap(null);
}
$('#measure span').text('0');
}
function measureCalc(){
var points='';
measure.ll.forEach(function(latLng,ind){
/* build a string of points in (x,y|x,y|x,y|x,y) format */
points+=latLng.lng()+','+latLng.lat()+'|';
});
points=points.slice(0,points.length-1);
/* send a getJSON request to our webserver to get length and area measurements */
$.getJSON('http://api.geojason.info/v1/ws_geo_length_area.php?format=json&in_srid=4326&out_srid=2264&points='+points+'&callback=?',function(data){
if (parseInt(data.total_rows)>0){
/* calculate and inject values in their corresponding "span"s */
//var length = parseFloat(data.rows[0].row.length);
var area = parseFloat(data.rows[0].row.area);
//$('#measure-area-sqft').text(area.toFixed(0));
$('#measure-area-acres').text((area/43560).toFixed(3));
//$('#measure-length-feet').text(length.toFixed(0));
//$('#measure-length-meters').text((length*0.3048).toFixed(1));
}
});
}
</script>
</head>
<body onload="Init();">
<div id="header">Home - Back to Demos</div>
<h2>Line Length and Polygon Area with Google Maps API v3</h2>
<div id="map-canvas"></div>
<div id="content">
<p></p>
<div><input type="button" onclick="measureReset();" value="Clear Measure" /></div>
<div id="measure">
<div>Length: <span id="measure-length-feet">0</span> ft.</div>
<div>Length: <span id="measure-length-meters">0</span> met.</div>
<div>Area: <span id="measure-area-sqft">0</span> ft.&sup2</div>
<div>Area: <span id="measure-area-acres">0</span> ac.</div>
</div>
</div>
</body>
</html>
I guess you need to bind each marker position to a vertex in your polyline (use bindTo method ) - a good example is here
http://gmaps-utility-gis.googlecode.com/svn/trunk/v3test/mvc/poly_bind.html
simple examples here
http://gmaps-samples-v3.googlecode.com/svn/trunk/rectangle-overlay/rectangle-overlay.html
http://gmaps-samples-v3.googlecode.com/svn/trunk/circle-overlay/circle-overlay.html

Google maps API : V2 : Custom infowindow with bindInfoWindowHtml

The API documentation gave me hopes last night with "bindInfoWindowHtml".
But it doesn't seem to replace the default infoWindow, even when you provide your own class etc.
I have tried using other ideas like the labeledmarker. But it doesn't support draggable markers. Hence can't use it in my application.
Here is the sample code which shows the info. window inside, the original bubble.
Isn't there a way to override that window as well !
`
<style type="text/css">
.infoWindowCustomClass
{
width: 500px;
height: 500px;
background-color: #CAEE96;
color: #666;
}
</style>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Google Maps JavaScript API Example</title>
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key="" type="text/javascript"></script>
<script type="text/javascript">
function load() {
if (GBrowserIsCompatible())
{
// Create our "tiny" marker icon
var blueIcon = new GIcon(G_DEFAULT_ICON);
blueIcon.image = "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png";
// Set up our GMarkerOptions object
markerOptions = { icon:blueIcon };
var map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(33.968064,-83.377047), 13);
markerOptions.title = "fart";
var point = new GLatLng(33.968064,-83.377047);
var marker = new GMarker(point);
var tempName = document.getElementById("infoWindowCustom");
marker.bindInfoWindowHtml(tempName);
map.addOverlay(marker);
}
}
</script>`
And here is the DIV -
<DIV id="infoWindowCustom" name="infoWindowCustom" class="infoWindowCustomClass">
Name : <TEXTAREA NAME="nameID" ID="nameID" ROWS="2" COLS="25"></TEXTAREA>
Comments : <TEXTAREA NAME="commentsID" ID="commentsID" ROWS="4" COLS="25"></TEXTAREA>
</DIV>
Solved it as below -
Instead of binding it as above, I take the lang/lats and launch a div at that place.
That seems to work just fine.