Google map API image overlay in vector map with tilt and heading - google-maps

I place the image on the map using custom overlay example in
https://developers.google.com/maps/documentation/javascript/customoverlays
but when i rotate the map, overlay deforms. Is it possible to fix it?
enter image description here
I understand that this is happening because in the draw method
processing is carried out at two points - SouthWest and NorthEast.
draw() {
// We use the south-west and north-east
// coordinates of the overlay to peg it to the correct position and size.
// To do this, we need to retrieve the projection from the overlay.
const overlayProjection = this.getProjection();
// Retrieve the south-west and north-east coordinates of this overlay
// in LatLngs and convert them to pixel coordinates.
// We'll use these coordinates to resize the div.
const sw = overlayProjection.fromLatLngToDivPixel(
this.bounds.getSouthWest()
);
const ne = overlayProjection.fromLatLngToDivPixel(
this.bounds.getNorthEast()
);
// Resize the image's div to fit the indicated dimensions.
if (this.div) {
this.div.style.left = sw.x + "px";
this.div.style.top = ne.y + "px";
this.div.style.width = ne.x - sw.x + "px";
this.div.style.height = sw.y - ne.y + "px";
this.div.style.transform = 'rotate(' + this.angle + 'deg)';
}
Because of this, the image is deformed.
But I don't know how to set four vertices for an image.

i solved it using WebGLOverlayView
https://developers.google.com/maps/documentation/javascript/webgl/webgl-overlay-view
const webGLOverlayView = new google.maps.WebGLOverlayView();
webGLOverlayView.onAdd = () => {
scene = new THREE.Scene();
const fov = 180;
const aspect = 2; // the canvas default
const near = 1;
const far = 1;
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
const width = 800;
const height = 400;
const widthSegments = 800;
const heightSegments = 400;
const geometry = new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments)
const loader = new THREE.TextureLoader();
const texture = loader.load('img920C.jpg');
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.NearestMipMapNearestFilter
texture.anisotropy = 16
var material = new THREE.MeshBasicMaterial( { map: texture, } );
var cube = new THREE.Mesh( geometry, material );
cube.name = 'cube';
scene.add( cube );
}
webGLOverlayView.onContextRestored = ({gl}) => {
renderer = new THREE.WebGLRenderer({
canvas: gl.canvas,
context: gl,
antialias: false,
...gl.getContextAttributes(),
});
renderer.autoClear = false;
renderer.antialias = false;
const controls = new THREE.OrbitControls(camera, renderer.domElement);
};
webGLOverlayView.onDraw = ({gl, transformer}) => {
const latLngAltitudeLiteral = {
lat: marker.getPosition().lat,
lng: marker.getPosition().lng,
altitude: 0
}
const matrix = transformer.fromLatLngAltitude(latLngAltitudeLiteral);
camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
webGLOverlayView.requestRedraw();
renderer.render(scene, camera);
renderer.resetState();
}
webGLOverlayView.setMap(map)
}
It looks like this is the correct approach. But what about mouse events, I don't know. I would like to receive a mousedown click event? for webGLOverlayView.
Something similar to:
webGLOverlayView.addListener('click', function(){console.log("ok")});

Related

How to get the terrain height at mouse position when in 2D map?

I'm using Cesium in 2D mode.
This is the code I'm using to take the terrain height at mouse position:
cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic( position );
var longitudeString = Cesium.Math.toDegrees(cartographic.longitude).toFixed(10);
var latitudeString = Cesium.Math.toDegrees(cartographic.latitude).toFixed(10);
mapPointerLatitude = latitudeString.slice(-15);
mapPointerLongitude = longitudeString.slice(-15);
var tempHeight = cartographic.height;
if( tempHeight < 0 ) tempHeight = 0;
mapPointerHeight = tempHeight.toFixed(2);
Where position came from a Cesium.ScreenSpaceEventType.MOUSE_MOVE event:
if ( mapStyle === '2D' ) {
var position = viewer.camera.pickEllipsoid(movement.endPosition, scene.globe.ellipsoid);
if (position) {
return position;
}
}
if ( mapStyle === '3D' ) {
var ray = viewer.camera.getPickRay(movement.endPosition);
var position = viewer.scene.globe.pick(ray, viewer.scene);
if (Cesium.defined(position)) {
return position;
}
}
When the map is in 3D mode (Cesium.SceneMode.SCENE3D) I have the height value in tempHeight but when the map is in Cesium.SceneMode.SCENE2D this value is always zero. What I'm doing wrong?
EDIT
Don't know if this is the best way but I get it to work putting my code inside this:
cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic( position );
var positions = [ cartographic ];
var promise = Cesium.sampleTerrain(terrainProvider, 11, positions);
Cesium.when(promise, function( updatedPositions ) {
// PUT ALL HERE
});
You can actually try Cesium.sampleTerrainMostDetailed to get more precise altitude.
// Query the terrain height of two Cartographic positions
const terrainProvider = Cesium.createWorldTerrain();
const positions = [
Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
Cesium.Cartographic.fromDegrees(87.0, 28.0)
];
Cesium.sampleTerrainMostDetailed(terrainProvider, positions).then((updatedPositions) => {
// positions[0].height and positions[1].height have been updated.
// updatedPositions is just a reference to positions.
})
https://cesium.com/learn/cesiumjs/ref-doc/global.html?classFilter=sample#sampleTerrainMostDetailed

image using cordova plugin looks horrible on canvas

i am using cordova for my ios app which captures the image
the code looks
navigator.camera.getPicture(onSuccessCamera, onFailureCamera, {
quality: 25,
destinationType: navigator.camera.DestinationType.DATA_URL,
correctOrientation: true,
allowEdit:false
});
function onSuccessCamera(imageURI) {
var imgData = "data:image/jpeg;base64," + imageURI;
uploadFile(imgData);
}
function uploadFile(file){
var c=document.getElementById("picture");
c.width = window.innerWidth-50;//offset to prevent image flowing out of frame
// window.alert(window.innerWidth+":"+ window.innerHeight);414:736
c.height = "330";//window.innerHeight;//this.height;
var ctx=c.getContext("2d");
var showImg = new Image();
showImg.onload = function(){
var ratio = 1;
if (this.height > c.height) {
ratio = c.height/this.height;
}
else if (this.width>c.width) {
ratio = c.width/this.width;
}
ctx.drawImage(this,0,0, this.width*ratio, this.height*ratio);
// window.alert(c.width + ':' + c.height);
};
showImg.src = file;
}
i dont know why the image looks so horrible
It's because of the retina screen.
Make the height and width of the canvas to be the double, but then style it to be half pixels
c.width = (window.innerWidth-50)*2;//offset to prevent image flowing out of frame
c.height = 330*2;//window.innerHeight;//this.height;
c.style.width = (c.width/2)+"px";
c.style.height = (c.height/2)+"px";
Also, you can consider using a higher value on quality camera option.

He's dead, Jim: is there a memory limit on the number of circles that can be drawn on a Google map?

On a map of the USA, I have been asked to draw 50,000 circles each with a 5000-yard radius.
The lat-lon locations are scattered throughout the country, but a large number of these circles overlap. Opacity is set to 0.05; regions with many superimposed circles become more opaque.
The circles start to appear, but after about 30 seconds Chrome crashes, displaying the "He's dead, Jim" message.
About the error message:
According to Error: "He's Dead, Jim!":
You might see the “He’s Dead, Jim!” message in a tab if:
You don’t have enough memory available to run the tab. Computers rely on memory to run apps, extensions, and programs. Low memory
can cause them to run slowly or stop working.
You stopped a process using Google Chrome's Task Manager, the system's task manager, or a command line tool.
It evidently occurs since you are trying to render 50k objects. In order to render such amount of objects I would recommend to consider Overlay approach. In that case the performance could be improved considerably since city icons could ve rendered via canvas element instead of div ones.
Having said that, the below example demonstrates how to render multiple amount of cities (1000 cities ) using the described approach:
var overlay;
USCitiesOverlay.prototype = new google.maps.OverlayView();
function USCitiesOverlay(map) {
this._map = map;
this._cities = [];
this._radius = 6;
this._container = document.createElement("div");
this._container.id = "citieslayer";
this.setMap(map);
this.addCity = function (lat, lng) {
this._cities.push({position: new google.maps.LatLng(lat,lng)});
};
}
USCitiesOverlay.prototype.createCityIcon = function (id,pos) {
/*var cityIcon = document.createElement('div');
cityIcon.setAttribute('id', "cityicon_" + id);
cityIcon.style.position = 'absolute';
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
cityIcon.style.width = cityIcon.style.height = (this._radius * 2) + 'px';
cityIcon.className = "circleBase city";
return cityIcon;*/
var cityIcon = document.createElement('canvas');
cityIcon.id = 'cityicon_' + id;
cityIcon.width = cityIcon.height = this._radius * 2;
cityIcon.style.width = cityIcon.width + 'px';
cityIcon.style.height = cityIcon.height + 'px';
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
cityIcon.style.position = "absolute";
var centerX = cityIcon.width / 2;
var centerY = cityIcon.height / 2;
var ctx = cityIcon.getContext('2d');
ctx.fillStyle = 'rgba(160,16,0,0.6)';
ctx.beginPath();
ctx.arc(centerX, centerY, this._radius, 0, Math.PI * 2, true);
ctx.fill();
return cityIcon;
};
USCitiesOverlay.prototype.ensureCityIcon = function (id,pos) {
var cityIcon = document.getElementById("cityicon_" + id);
if(cityIcon){
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
return cityIcon;
}
return this.createCityIcon(id,pos);
};
USCitiesOverlay.prototype.onAdd = function () {
var panes = this.getPanes();
panes.overlayLayer.appendChild(this._container);
};
USCitiesOverlay.prototype.draw = function () {
var zoom = this._map.getZoom();
var overlayProjection = this.getProjection();
var container = this._container;
this._cities.forEach(function(city,idx){
var xy = overlayProjection.fromLatLngToDivPixel(city.position);
var cityIcon = overlay.ensureCityIcon(idx,xy);
container.appendChild(cityIcon);
});
};
USCitiesOverlay.prototype.onRemove = function () {
this._container.parentNode.removeChild(this._container);
this._container = null;
};
function getRandomInterval(min, max) {
return Math.random() * (max - min) + min;
}
function generateCityMap(count) {
var citymap = [];
var minPos = new google.maps.LatLng(49.25, -123.1);
var maxPos = new google.maps.LatLng(34.052234, -74.005973);
for(var i = 0; i < count;i++)
{
var lat = getRandomInterval(minPos.lat(),maxPos.lat());
var lng = getRandomInterval(minPos.lng(),maxPos.lng());
var population = getRandomInterval(10000,1000000);
citymap.push({
location: new google.maps.LatLng(lat, lng),
population: population
});
}
return citymap;
}
function initialize() {
var mapOptions = {
zoom: 4,
center: new google.maps.LatLng(37.09024, -95.712891),
mapTypeId: google.maps.MapTypeId.TERRAIN
};
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
overlay = new USCitiesOverlay(map);
overlay.addCity(40.714352, -74.005973); //chicago
overlay.addCity(40.714352, -74.005973); //newyork
overlay.addCity(34.052234, -118.243684); //losangeles
overlay.addCity(49.25, -123.1); //vancouver
var citymap = generateCityMap(1000);
citymap.forEach(function(city){
overlay.addCity(city.location.lat(), city.location.lng());
});
}
google.maps.event.addDomListener(window, 'load', initialize);
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true"></script>
<div id="map-canvas"></div>

Three.js image disappearing after a few calls to requestAnimationFrame()

I am trying to get some basic movement/refreshing working in Three.js. I've cut the problem back to the following code.
A sphere displays fine first render, and twice (dictate by Nb), but the image vanishes for 3 renders that are called via requestAnimationFrame(simulate) (for 4 it displays then disappears); Am I missing something in how repeated rendering should happen ?
var sphere, WIDTH, HEIGHT, VIEW_ANGLE, ASPECT, NEAR, FAR, renderer, camera, scene, sphereMaterial, radius, sphere, pointLight, container;
function init() {
WIDTH = 400;
HEIGHT = 300;
VIEW_ANGLE = 45;
ASPECT = WIDTH / HEIGHT;
NEAR = 0.1;
FAR = 10000;
container = $('#container');
renderer = new THREE.WebGLRenderer();
camera = new THREE.PerspectiveCamera( VIEW_ANGLE,
ASPECT,
NEAR,
FAR );
scene = new THREE.Scene();
camera.position.z = 200;
renderer.setSize( WIDTH, HEIGHT );
container.append(renderer.domElement);
sphereMaterial = new THREE.MeshLambertMaterial(
{
color: 0xCC0000
});
radius = 50; segments = 16; rings = 16;
sphere = new THREE.Mesh(
new THREE.SphereGeometry(radius, segments, rings),
sphereMaterial);
//sphere.position.z -= 100;
scene.add(sphere);
scene.add(camera);
pointLight = new THREE.PointLight( 0xFFFFFF );
pointLight.position.x = 10;
pointLight.position.y = 50;
pointLight.position.z = 130;
scene.add(pointLight);
};
var Nb = 3;
var j = 0;
function simulate() {
console.log("simulate " + sphere.position.z);
if (j == Nb) { return; }
j++;
//sphere.position.z -= 1;
render();
requestAnimationFrame(simulate);
};
function render() {
console.log("rendering" + sphere.position.z);
renderer.render(scene,camera);
};
init();
simulate();`
This is solved after an update of Chromium browser in this case (and possible related libs/drivers) to version 25.0.1346.160. Isolated by using jsfiddle as shown above by Tomalak.

HTML5 canvas: is there a way to resize image with "nearest neighbour" resampling?

I have some JS that makes some manipulations with images. I want to have pixelart-like graphics, so I had to enlarge original images in graphics editor.
But I think it'd be good idea to make all the manipulations with the small image and then enlarge it with html5 functionality. This will save bunch of processing time (because now my demo (warning: domain-name may cause some issues at work etc) loads extremely long in Firefox, for example).
But when I try to resize the image, it gets resampled bicubically. How to make it resize image without resampling? Is there any crossbrowser solution?
image-rendering: -webkit-optimize-contrast; /* webkit */
image-rendering: -moz-crisp-edges /* Firefox */
http://phrogz.net/tmp/canvas_image_zoom.html can provide a fallback case using canvas and getImageData. In short:
// Create an offscreen canvas, draw an image to it, and fetch the pixels
var offtx = document.createElement('canvas').getContext('2d');
offtx.drawImage(img1,0,0);
var imgData = offtx.getImageData(0,0,img1.width,img1.height).data;
// Draw the zoomed-up pixels to a different canvas context
for (var x=0;x<img1.width;++x){
for (var y=0;y<img1.height;++y){
// Find the starting index in the one-dimensional image data
var i = (y*img1.width + x)*4;
var r = imgData[i ];
var g = imgData[i+1];
var b = imgData[i+2];
var a = imgData[i+3];
ctx2.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
ctx2.fillRect(x*zoom,y*zoom,zoom,zoom);
}
}
More: MDN docs on image-rendering
I wrote a NN resizing script a while ago using ImageData (around line 1794)
https://github.com/arahaya/ImageFilters.js/blob/master/imagefilters.js
You can see a demo here
http://www.arahaya.com/imagefilters/
unfortunately the builtin resizing should be slightly faster.
This CSS on the canvas element works:
image-rendering: pixelated;
This works in Chrome 93, as of September 2021.
You can simply set context.imageSmoothingEnabled to false. This will make everything drawn with context.drawImage() resize using nearest neighbor.
// the canvas to resize
const canvas = document.createElement("canvas");
// the canvas to output to
const canvas2 = document.createElement("canvas");
const context2 = canvas2.getContext("2d");
// disable image smoothing
context2.imageSmoothingEnabled = false;
// draw image from the canvas
context2.drawImage(canvas, 0, 0, canvas2.width, canvas2.height);
This has better support than using image-rendering: pixelated.
I'll echo what others have said and tell you it's not a built-in function. After running into the same issue, I've made one below.
It uses fillRect() instead of looping through each pixel and painting it. Everything is commented to help you better understand how it works.
//img is the original image, scale is a multiplier. It returns the resized image.
function Resize_Nearest_Neighbour( img, scale ){
//make shortcuts for image width and height
var w = img.width;
var h = img.height;
//---------------------------------------------------------------
//draw the original image to a new canvas
//---------------------------------------------------------------
//set up the canvas
var c = document.createElement("CANVAS");
var ctx = c.getContext("2d");
//disable antialiasing on the canvas
ctx.imageSmoothingEnabled = false;
//size the canvas to match the input image
c.width = w;
c.height = h;
//draw the input image
ctx.drawImage( img, 0, 0 );
//get the input image as image data
var inputImg = ctx.getImageData(0,0,w,h);
//get the data array from the canvas image data
var data = inputImg.data;
//---------------------------------------------------------------
//resize the canvas to our bigger output image
//---------------------------------------------------------------
c.width = w * scale;
c.height = h * scale;
//---------------------------------------------------------------
//loop through all the data, painting each pixel larger
//---------------------------------------------------------------
for ( var i = 0; i < data.length; i+=4 ){
//find the colour of this particular pixel
var colour = "#";
//---------------------------------------------------------------
//convert the RGB numbers into a hex string. i.e. [255, 10, 100]
//into "FF0A64"
//---------------------------------------------------------------
function _Dex_To_Hex( number ){
var out = number.toString(16);
if ( out.length < 2 ){
out = "0" + out;
}
return out;
}
for ( var colourIndex = 0; colourIndex < 3; colourIndex++ ){
colour += _Dex_To_Hex( data[ i+colourIndex ] );
}
//set the fill colour
ctx.fillStyle = colour;
//---------------------------------------------------------------
//convert the index in the data array to x and y coordinates
//---------------------------------------------------------------
var index = i/4;
var x = index % w;
//~~ is a faster way to do 'Math.floor'
var y = ~~( index / w );
//---------------------------------------------------------------
//draw an enlarged rectangle on the enlarged canvas
//---------------------------------------------------------------
ctx.fillRect( x*scale, y*scale, scale, scale );
}
//get the output image from the canvas
var output = c.toDataURL("image/png");
//returns image data that can be plugged into an img tag's src
return output;
}
Below is an example of it in use.
Your image would appear in the HTML like this:
<img id="pixel-image" src="" data-src="pixel-image.png"/>
The data-src tag contains the URL for the image you want to enlarge. This is a custom data tag. The code below will take the image URL from the data tag and put it through the resizing function, returning a larger image (30x the original size) which then gets injected into the src attribute of the img tag.
Remember to put the function Resize_Nearest_Neighbour (above) into the <script> tag before you include the following.
function Load_Image( element ){
var source = element.getAttribute("data-src");
var img = new Image();
img.addEventListener("load", function(){
var bigImage = Resize_Nearest_Neighbour( this, 30 );
element.src = bigImage;
});
img.src = source;
}
Load_Image( document.getElementById("pixel-image") );
There is no built-in way. You have to do it yourself with getImageData.
Based on Paul Irish's comment:
function resizeBase64(base64, zoom) {
return new Promise(function(resolve, reject) {
var img = document.createElement("img");
// once image loaded, resize it
img.onload = function() {
// get image size
var imageWidth = img.width;
var imageHeight = img.height;
// create and draw image to our first offscreen canvas
var canvas1 = document.createElement("canvas");
canvas1.width = imageWidth;
canvas1.height = imageHeight;
var ctx1 = canvas1.getContext("2d");
ctx1.drawImage(this, 0, 0, imageWidth, imageHeight);
// get pixel data from first canvas
var imgData = ctx1.getImageData(0, 0, imageWidth, imageHeight).data;
// create second offscreen canvas at the zoomed size
var canvas2 = document.createElement("canvas");
canvas2.width = imageWidth * zoom;
canvas2.height = imageHeight * zoom;
var ctx2 = canvas2.getContext("2d");
// draw the zoomed-up pixels to a the second canvas
for (var x = 0; x < imageWidth; ++x) {
for (var y = 0; y < imageHeight; ++y) {
// find the starting index in the one-dimensional image data
var i = (y * imageWidth + x) * 4;
var r = imgData[i];
var g = imgData[i + 1];
var b = imgData[i + 2];
var a = imgData[i + 3];
ctx2.fillStyle = "rgba(" + r + "," + g + "," + b + "," + a / 255 + ")";
ctx2.fillRect(x * zoom, y * zoom, zoom, zoom);
}
}
// resolve promise with the zoomed base64 image data
var dataURI = canvas2.toDataURL();
resolve(dataURI);
};
img.onerror = function(error) {
reject(error);
};
// set the img soruce
img.src = base64;
});
}
resizeBase64(src, 4).then(function(zoomedSrc) {
console.log(zoomedSrc);
});
https://jsfiddle.net/djhyquon/69/