Projection drift when rendering in WebGL over Google Map - google-maps

I am trying to implement a WebGL-based rendering on Google Map (api3) as I want to render a massive amount of dynamic geometries.
Basically, I create a google.maps.OverlayView attached with a WebGL canvas into the map.
However, I encountered some problem with the mapping of the projection. Basically, I extracted the "fromLatLngToPoint" function from the googlemap api as follows:
function fromLatLngToPoint(a){
var c={x:0,y:0},
d=this.j;
c.x=d.x+a.lng*this.B;
var e=oe(m.sin(re(a.lat)),-(1-1E-15),1-1E-15);
c.y=d.y+.5*m.log((1+e)/(1-e))*-this.F;
return c
}
function oe(a,b,c){null!=b&&(a=m.max(a,b));null!=c&&(a=m.min(a,c));return a}
function re(a){return m.PI/180*a}
Then I implemented it in my vertex shader based on the documentation in Google Map Coordinates.
Basically, I have a event listener to send the updated projection constants, the viewport bounds, and the zoom level to my shader.
Then my shader will calculate the new screen coordinates based on these inputs.
highp float e, x, y, offsetY, offsetX;
// projection transformation for target points
e = sin(p.y* PI/180.0);
y = prj_y + 0.5 * log((1.0+e)/(1.0-e))*(-F);
x = prj_x + p.x*B;
// projection transformation for offset (bounds)
e = sin(bound_y*PI/180.0);
offsetY = prj_y + 0.5 * log((1.0+e)/(1.0-e))*(-F);
offsetX = prj_x + bound_x*B;
// calculate actual pixel coord wrt zoom/numTiles
x = (x* numTiles - offsetX* numTiles);
y = (y* numTiles - offsetY* numTiles);
gl_PointSize = 5.0;
gl_Position = projectionMatrix * modelViewMatrix * vec4(x,y,0.0,1.0);
However, as shown in the screenshot below, it seems there are some errors? The rendered geometries are distorted. (I used the google map polygon api to render some of the geometries as comparison)
Screenshot Here
I am totally at a loss, what might be the reason for this distortion?
I am suspecting that the single precision in the shader is giving rise to the error. So I am wondering if there is any workaround?

It is hard to debug this piece of code and diagnose the cause of the issue. I would suggest you using the CanvasLayer library that hides all these concrete details of specifying the coordinates you want to draw the polygon. Rather you would be able to focus on your app code and functionality. The performance will be better in terms of projected image.

Related

Maps into Forge Viewer

Trying to follow the steps https://forge.autodesk.com/blog/add-mapbox-google-maps-forge-viewer but i can't place the model correctly on the map.
I am running the functions listed here: https://learn.microsoft.com/en-us/bingmaps/articles/bing-maps-tile-system:
LatLongToPixelXY(latitude, longitude, 7, out pixelX, out pixelY);
PixelXYToTileXY(pixelX, pixelY, out tileX, out tileY);
The result pixelX = 16225, pixelY = 12249, tileX = 63, tileY = 47.
I substitute the previous values:
map.position.set(16225,12249,-45);
class MapPlaneNode extends MapNode {
constructor(parentNode = null, mapView = null, location = MapNode.ROOT, level = 7, x = 63, y = 47)
The result is that the model comes out small and not positioned correctly. In the image, the red arrow is where the model is inserted, and the green arrow is where it should be.
image of result
What am I doing wrong?
Thank you very much
Positioning the model is a little tricky.
In the demo I created, I originally used world coordinates, where I set the root tile as level 0, and used the correct lat/long coordinate utils function to position the revit model in the correct location.
Unfortunately, the precision caused a rendering problem with the post-renderer (line edges were missing, and some strange z-fighting precision issues)...
so, I decided to hack the level, and move the map into the position I wanted and center the revit model at origin 0,0,0.
This made things a lot more manual and rather tricky, but it got around the rendering issue and also limited the user into a small area in the world, which I preferred.
I suggest changing the root tile back to zero, and adjusting the model position globaloffset to the value of the lat/long W84 utils. See the blog post and also the coordinates section of the geo-three repo, for more details here: https://github.com/tentone/geo-three#coordinates
Found a trick to adjust the map. It is still manual but it's fairly quick:
Calculate Tile X and Y (you did that step already, it's just for reference):
Copy the TileSystem class from the the link bing-maps-tile-system you posted into https://dotnetfiddle.net/
(you'll also need to add: using System.Text)
Change the main as follows
public static void Main()
{
int pixelX, pixelY, tileX, tileY;
TileSystem.LatLongToPixelXY(YOUR LAT HERE, YOUR LONG HERE, 7, out pixelX, out pixelY);
Console.WriteLine("LatLongToPixelXY: " + pixelX.ToString() + ", " + pixelY.ToString());
TileSystem.PixelXYToTileXY(pixelX, pixelY, out tileX, out tileY);
Console.WriteLine("PixelXYToTileXY: " + tileX.ToString() + ", " + tileY.ToString());
}
This will give you the TileX and Tile Y that you'll need to replace in the Extension.
Calculate Position
In the Extension set the X, Y position to 0,0, and the adjust the Z so that the map is below your model
map.position.set(0, 0, z);
Run the Extension and see where your project lands on the map. Now locate this landing point in Google maps (I found it useful at this stage to search the map using a corner between two streets by entering for example: Parker St & Wilson Rd). When you've found it, click on the landing point in Google map to place a Marker, then right-click on the marker and select Measure Distance. You will have to measure the distance to your destination both vertically, and horizontally (not directly to it). For example you'll get dH = 43.5km and dV = 17.8km
And this is were the magic happens: Multiply both numbers by 3400 if your distance is in km (or by 2113 if you distance is in miles) and set the position with those values:
dH * 3400 = 147900
dV * 3400 = 60520
If your destination is to the E or S use positive values.
If your destination is to the W or N use negative values
map.position.set(147900, -60520, z);
Now it won't be perfect, but it'll be close enough to finish adjusting the value manually.

In Starling, how do you transform Filters to match the target Sprite's rotation & position?

Let's say your Starling display-list is as follows:
Stage
|___MainApp
|______Canvas (filter's target)
Then, you decide your MainApp should be rotated 90 degrees and offset a bit:
mainApp.rotation = Math.PI * 0.5;
mainApp.x = stage.stageWidth;
But all of a sudden, the filter keeps on applying itself to the target (canvas) in the angle it was originally (as if the MainApp was still at 0 degrees).
(notice in the GIF how the Blur's strong horizontal value continues to only apply horizontally although the parent object turned 90 degrees).
What would need to be changed to apply the filter to the target object before it gets it's parents transform? That way (I'm assuming) the filter's result would get transformed by the parent objects.
Any guess as to how this could be done?
https://github.com/bigp/StarlingShaderIssue
(PS: the filter I'm actually using is custom-made, but this BlurFilter example shows the same issue I'm having with the custom one. If there's any patching-up to do in the shader code, at least it wouldn't necessarily have to be done on the built-in BlurFilter specifically).
I solved this myself with numerous trial and error attempts over the course of several hours.
Since I only needed the shader to run in either at 0 or 90 degrees (not actually tweened like the gif demo shown in the question), I created a shader with two specialized sets of AGAL instructions.
Without going in too much details, the rotated version basically requires a few extra instructions to flip the x and y fields in the vertex and fragment shader (either by moving them with mov or directly calculating the mul or div result into the x or y field).
For example, compare the 0 deg vertex shader...
_vertexShader = [
"m44 op, va0, vc0", // 4x4 matrix transform to output space
"mov posOriginal, va1", // pass texture positions to fragment program
"mul posScaled, va1, viewportScale", // pass displacement positions (scaled)
].join("\n");
... with the 90 deg vertex shader:
_vertexShader = [
"m44 op, va0, vc0", // 4x4 matrix transform to output space
"mov posOriginal, va1", // pass texture positions to fragment program
//Calculate the rotated vertex "displacement" UVs
"mov temp1, va1",
"mov temp2, va1",
"mul temp2.y, temp1.x, viewportScale.y", //Flip X to Y, and scale with viewport Y
"mul temp2.x, temp1.y, viewportScale.x", //Flip Y to X, and scale with viewport X
"sub temp2.y, 1.0, temp2.y", //Invert the UV for the Y axis.
"mov posScaled, temp2",
].join("\n");
You can ignore the special aliases in the AGAL example, they're essentially posOriginal = v0, posScaled = v1 variants and viewportScale = vc4constants, then I do a string-replace to change them back to their respective registers & fields ).
Just a human-readable trick I use to avoid going insane. \☻/
The part that I struggled with the most was calculating the correct scale to adjust the UV's scale (with proper detection to Stage / Viewport resize and render-texture size shifts).
Eventually, this is what I came up with in the AS3 code:
var pt:Texture = _passTexture,
dt:RenderTexture = _displacement.texture,
notReady:Boolean = pt == null,
star:Starling = Starling.current;
var finalScaleX:Number, viewRatioX:Number = star.viewPort.width / star.stage.stageWidth;
var finalScaleY:Number, viewRatioY:Number = star.viewPort.height / star.stage.stageHeight;
if (notReady) {
finalScaleX = finalScaleY = 1.0;
} else if (isRotated) {
//NOTE: Notice how the native width is divided with height, instead of same side. Weird, but it works!
finalScaleY = pt.nativeWidth / dt.nativeHeight / _imageRatio / paramScaleX / viewRatioX; //Eureka!
finalScaleX = pt.nativeHeight / dt.nativeWidth / _imageRatio / paramScaleY / viewRatioY; //Eureka x2!
} else {
finalScaleX = pt.nativeWidth / dt.nativeWidth / _imageRatio / viewRatioX / paramScaleX;
finalScaleY = pt.nativeHeight / dt.nativeHeight / _imageRatio / viewRatioY / paramScaleY;
}
Hopefully these extracted pieces of code can be helpful to others with similar shader issues.
Good luck!

How do I know if a Lat,Lng point is contained within a circle?

Ok pretty self explanatory. I'm using google maps and I'm trying to find out if a lat,long point is within a circle of radius say x (x is chosen by the user).
Bounding box will not work for this. I have already tried using the following code:
distlatLng = new google.maps.LatLng(dist.latlng[0],dist.latlng[1]);
var latLngBounds = circle.getBounds();
if(latLngBounds.contains(distlatLng)){
dropPins(distlatLng,dist.f_addr);
}
This still results in markers being places outside the circle.
I'm guess this is some simple maths requiring the calculation of the curvature or an area but I'm not sure where to begin. Any suggestions?
Unfortunately Pythagoras is no help on a sphere. Thus Stuart Beard's answer is incorrect; longitude differences don't have a fixed ratio to metres but depend on the latitude.
The correct way is to use the formula for great circle distances. A good approximation, assuming a spherical earth, is this (in C++):
/** Find the great-circle distance in metres, assuming a spherical earth, between two lat-long points in degrees. */
inline double GreatCircleDistanceInMeters(double aLong1,double aLat1,double aLong2,double aLat2)
{
aLong1 *= KDegreesToRadiansDouble;
aLat1 *= KDegreesToRadiansDouble;
aLong2 *= KDegreesToRadiansDouble;
aLat2 *= KDegreesToRadiansDouble;
double cos_angle = sin(aLat1) * sin(aLat2) + cos(aLat1) * cos(aLat2) * cos(aLong2 - aLong1);
/*
Inaccurate trig functions can cause cos_angle to be a tiny amount
greater than 1 if the two positions are very close. That in turn causes
acos to give a domain error and return the special floating point value
-1.#IND000000000000, meaning 'indefinite'. Observed on VS2008 on 64-bit Windows.
*/
if (cos_angle >= 1)
return 0;
double angle = acos(cos_angle);
return angle * KEquatorialRadiusInMetres;
}
where
const double KPiDouble = 3.141592654;
const double KDegreesToRadiansDouble = KPiDouble / 180.0;
and
/**
A constant to convert radians to metres for the Mercator and other projections.
It is the semi-major axis (equatorial radius) used by the WGS 84 datum (see http://en.wikipedia.org/wiki/WGS84).
*/
const int32 KEquatorialRadiusInMetres = 6378137;
Use Google Maps API geometry library to calculate distance between circle's center and your marker, and then compare it with your radius.
var pointIsInsideCircle = google.maps.geometry.spherical.computeDistanceBetween(circle.getCenter(), point) <= circle.getRadius();
It's very simple. You just have to calculate distance between centre and given point and compare it to radius. You can Get Help to calculate distance between two lat lang from here
The following code works for me: my marker cannot be dragged outside the circle, instead it just hangs at its edge (in any direction) and the last valid position is preserved.
The function is the eventhandler for the markers 'drag' event.
_markerDragged : function() {
var latLng = this.marker.getPosition();
var center = this.circle.getCenter();
var radius = this.circle.getRadius();
if (this.circleBounds.contains(latLng) &&
(google.maps.geometry.spherical.computeDistanceBetween(latLng, center) <= radius)) {
this.lastMarkerPos = latLng;
this._geocodePosition(latLng);
} else {
// Prevent dragging marker outside circle
// see (comments of) http://unserkaiser.com/code/google-maps-marker-check-if-in-circle/
// see http://www.mvjantzen.com/blog/?p=3190 and source code of http://mvjantzen.com/cabi/trips4q2012.html
this.marker.setPosition(this.lastMarkerPos);
}
},
Thanks to http://unserkaiser.com/code/google-maps-marker-check-if-in-circle/
and http://www.mvjantzen.com/blog/?p=3190 .
I've been a bit silly really. Thinking about it we can use Pythagorus' theorem.
We have a maximum distance away from a point (X miles), and two latitudes and two longitudes. If we form a triangle using these then we can solve for the distance from the point.
So say we know point1 with coordinates lat1,lng1 is the center of the circle and point2 with coordinates lat2,lng2 is the point we are trying to decide is in the circle or not.
We form a right angled triangle using a point determined by point1 and point2. This, point3 would have coordinates lat1,lng2 or lat2,lng1 (it doesn't matter which). We then calculate the differences (or if you prefer) distances - latDiff = lat2-lat1 and lngDiff = lng2-lng1
we then calculate the distance from the center using Pythagorus - dist=sqrt(lngDiff^2+latDiff^2).
We have to translate everything into meters so that it works correctly with google maps so miles are multiplied by 1609 (approx) and degrees of latitude/longitude by 111000 (approx). This isn't exactly accurate but it does an adequate job.
Hope that all makes sense.

html5 canvas: server or client fast transforms from longitude latitude to pixel values

I am working on some maps and animations using the html5 canvas element (example visible here.) I would like to be able to efficiently generate pixel-valued linestrings (x1, y1, x2, y2), ideally from PostGIS, based on the geometry of the canvas. That is, in pseudo-geojson:
"Coordinates":"[[-122.0, 35.0], [-121.0, 36.0]]"
might output in the case of a function passed a 100px canvas-width parameter:
"Pixels":"[[30, 40],[50,60]]"
I would like to eventually enable urls like:
www.example.com/canvas_size:200/box_width:3-miles/center_point:lon|lat
so I assume this has to be done dynamically. How have other people tackled this kind of thing? It has occurred to me is to maybe treat the entire world as a 20,000,000 pixel canvas, store pre-transformed data in pixel form, then just offset it with client-side arithmetic. Any suggestions welcome, including approaches far afield of my first thoughts. If anyone is familiar with if or how this is done in Cartagen, would appreciate a pointer to a library or function or two.
Try this code where the pixels will adapt to the height and width of the canvas:
function getPoint(latitude, longitude, canvas_width, canvas_height) {
var obj = {};
obj.x = (latitude * canvas_height / 180.0) + (canvas_height / 2);
obj.y = (longitude * canvas_width / 360.0) + (canvas_width / 2);
return obj;
}

What is a good algorithm for mapping GPS coordinates to screen locations when using Google maps?

I need an algorithm that will convert a GPS coordinate to a screen location on a displayed google map. I would think this would be simple- get the coordinates for the four corners of the displayed map, find the differential and create a scaling factor for a pixel location on the screen. Is this correct or am I missing something. I'm know this has been done ad nauseum but I am hoping I can hear from someone who has implemented it successfully or has a resource for implementing it.
Basically you need the code for Transverse Mercator projection (which is used by Google maps and others). Here's a C# snippet I used my Kosmos software:
public Point2<int> ConvertMapToViewCoords (Point2<double> mapCoords)
{
double x = (mapCoords.X - MapPosition.Longitude) / resolution;
double y = Math.Log (Math.Tan (Math.PI*(0.25 + mapCoords.Y/360)))*u180dPiResolution;
return new Point2<int> ((int)(x + viewWidthHalf), (int)((y0 - y) + viewHeightHalf));
}
variables used:
double resolution = 360.0 / (Math.Pow (2, MapPosition.ZoomFactor) * 256);
double u180dPiResolution = 40.7436654315252 * Math.Pow(2, MapPosition.ZoomFactor);
double y0 = Math.Log(Math.Tan(Math.PI * (0.25 + MapPosition.Latitude / 360))) * u180dPiResolution;
float viewWidthHalf = ViewWidth / 2.0f;
float viewHeightHalf = ViewHeight / 2.0f;
ZoomFactor is Google zoom level (see http://laudontech.com/GISBlog/?p=28).
BTW the same code works for OpenStreetMap, Yahoo Maps etc., since they all use the same projection and tiling system.
The Google Maps API lets you do stuff like this.
Here is some JS code I've written using the APIs that does something similar:
var map = new GMap2(document.getElementById("map"));
//...
var location = new GLatLng(37.771008, -122.41175);
map.setCenter(location);
var marker = new GMarker(location);
var overlay_caption = "Our location!";
GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml(overlay_caption);
});
map.addOverlay(marker);
marker.openInfoWindowHtml(overlay_caption);
You can also redirect the page to a new map with a URL like this:
http://maps.google.com/maps?q=37.771008,+-122.41175+(You+can+insert+your+text+here)&iwloc=A&hl=en
If you need the pixel coordinate of a latitude/longitude position of a current instance of Google Maps you may use the fromLatLngToDivPixel() function.
Assuming map is an instance of an initialized GMap2:
var location = new GLatLng(37.771008, -122.41175);
var point = map.fromLatLngToDivPixel(location);
alert("X: " + point.x + ", Y: " + point.y);
Depending on your needs, see also fromLatLngToContainerPixel.