I'm using the Google Maps API to embed a map in a web page. The map fills the entire screen, but there's ~400px worth of content on the left side of the screen that mostly covers the map. When I want to pan the map, that area to the left should be treated as though it isn't visible.
I came up with the following code to calculate the "usable part" of the map, which I'd like to be 50px in from the map's edge, and should also avoid the 400px area to the left side of the map:
panIfNotClose = function(latLngs){
if(latLngs.length === 0)
return;
// Get some important values that may not be available yet
// http://stackoverflow.com/a/2833015/802335
var bounds = map.getBounds();
if(!bounds)
return setTimeout(panIfNotClose.bind(this, latLngs), 10);
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var proj = overlay.getProjection();
if(!proj)
return setTimeout(panIfNotClose.bind(this, latLngs), 10);
// Calculate the "usable part" of the map
var center = bounds.getCenter();
var northEast = bounds.getNorthEast();
var southWest = bounds.getSouthWest();
var northEastPx = proj.fromLatLngToContainerPixel(northEast);
var southWestPx = proj.fromLatLngToContainerPixel(southWest);
var menuPadding = 400;
var newNorthEastPx = new google.maps.Point(northEastPx.x + 50, northEastPx.y + 50);
var newSouthWestPx = new google.maps.Point(southWestPx.x - (50 + menuPadding), southWestPx.y - 50);
var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);
// Decide if any of the new LatLngs are far enough away that the map should move
var shouldMove = false;
var targetBounds = new google.maps.LatLngBounds();
for(var i = 0; i < latLngs.length; i++){
targetBounds.extend(latLngs[i]);
if(!centerBounds.contains(latLngs[i]))
shouldMove = true;
}
// If the LatLngs aren't all near the center of the map, pan to it
if(latLngs.length === 1){
if(shouldMove || map.getZoom() !== 18)
map.panTo(latLngs[0]);
map.setZoom(18);
}else{
var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
if(shouldMove || map.getZoom() !== targetZoom)
map.panTo(targetBounds.getCenter());
map.setZoom(targetZoom);
}
}
This code should test the "valid" area to make sure all the given LatLngs fit inside, but it doesn't yet make any changes to panTo to "move" the center 200px to the right to account for the 400px worth of content on the left.
The code doesn't work as I intended, but I'm not sure why. I suspect I'm probably doing something wrong when converting from a LatLng to a Point or vice-versa. I may also be doing far more work than is necessary, although I can't think of a way to simplify it.
This turned out to be a pretty simple mistake. In the line var centerBounds = new google.maps.LatLngBounds(newSouthWest, newSouthWest);, I accidentally used the same LatLng twice, rather than the northeast and southwest corners. I also had a couple simple arithmetic issues when calculating newNorthEastPx and newSouthWestPx. Drawing a google.maps.Rectangle using centerBounds helped work that out quickly and easily. For anyone interested, here's the end result:
function panIfNotClose(latLngs, zoomOnly){
if(latLngs.length === 0)
return;
if(typeof latLngs !== "object")
latLngs = [latLngs];
// Calculate the "middle half" of the map
var bounds = map.getBounds();
if(!bounds) // http://stackoverflow.com/a/2833015/802335
return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var proj = overlay.getProjection();
if(!proj)
return setTimeout(panIfNotClose.bind(this, latLngs, zoomOnly), 10);
var center = bounds.getCenter();
var northEast = bounds.getNorthEast();
var southWest = bounds.getSouthWest();
var northEastPx = proj.fromLatLngToContainerPixel(northEast);
var southWestPx = proj.fromLatLngToContainerPixel(southWest);
var menuPadding = 0;
if($navBar.css("display") !== "none")
menuPadding = 400;
var newNorthEastPx = new google.maps.Point(northEastPx.x - 100, northEastPx.y + 100);
var newSouthWestPx = new google.maps.Point(southWestPx.x + (100 + menuPadding), southWestPx.y - 100);
var newNorthEast = proj.fromContainerPixelToLatLng(newNorthEastPx);
var newSouthWest = proj.fromContainerPixelToLatLng(newSouthWestPx);
centerBounds = new google.maps.LatLngBounds(newSouthWest, newNorthEast);
var shouldMove = false;
var targetBounds = new google.maps.LatLngBounds();
for(var i = 0; i < latLngs.length; i++){
targetBounds.extend(latLngs[i]);
if(!centerBounds.contains(latLngs[i]))
shouldMove = true;
}
// If the marker isn't near the center of the map, pan to it
if(latLngs.length === 1){
if(!zoomOnly && (shouldMove || map.getZoom() !== 18))
map.panTo(correctCenter(latLngs[0], proj));
map.setZoom(18);
}else{
var targetZoom = Math.min(getBoundsZoomLevel(targetBounds), 18);
if(!zoomOnly && (shouldMove || map.getZoom() !== targetZoom))
map.panTo(correctCenter(targetBounds.getCenter(), proj));
map.setZoom(targetZoom);
}
}
function correctCenter(latLng, proj){
// $navBar references a jQuery pointer to a DOM element
if($navBar.css("display") === "none")
return latLng;
var latLngPx = proj.fromLatLngToContainerPixel(latLng);
var newLatLngPx = new google.maps.Point(latLngPx.x - 200, latLngPx.y)
return proj.fromContainerPixelToLatLng(newLatLngPx);
}
// Adapted from http://stackoverflow.com/a/13274361/802335
function getBoundsZoomLevel(bounds){
// $mapCanvas is a jQuery reference to the div containing the Google Map
var mapDim = {
width: $mapCanvas.width() * .6,
height: $mapCanvas.height() * .6
};
var ZOOM_MAX = 18;
function latRad(lat){
var sin = Math.sin(lat * Math.PI / 180);
var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;
return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;
}
function zoom(mapPx, fraction){
return Math.floor(Math.log(mapPx / 256 / fraction) / Math.LN2);
}
var ne = bounds.getNorthEast();
var sw = bounds.getSouthWest();
var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;
var lngDiff = ne.lng() - sw.lng();
var lngFraction = ((lngDiff < 0) ? (lngDiff + 360) : lngDiff) / 360;
var latZoom = zoom(mapDim.height, latFraction);
var lngZoom = zoom(mapDim.width, lngFraction);
return Math.min(latZoom, lngZoom, ZOOM_MAX);
}
Related
I'm running into an issue with my code to make a fillet shape because the code will seemingly randomly flip the coordinates of the intersection point for reasons I can't find. Even when it is using the same equations, and the same vector is found to be the shortest, just changing the waypoint location very slightly changes the intersection point massively. I've run through the code a bunch of times, and can't tell why this would happen. I can't post images without 10 reputation, but if you run my code and change only the x coordinate for waypoint1 from 250 to 150 it has been breaking. Any help would be greatly appreciated!
var straightpath1 = new Path();
var straightpath2 = new Path();
//User inputs waypoints
var waypoint1 = new Point(250,450);
var waypoint2 = new Point(300,300);
var waypoint3 = new Point(500,600);
//Creates vectors to better visualize waypoints
straightpath1.add(waypoint1);
straightpath1.add(waypoint2);
straightpath1.strokeColor = 'red';
straightpath2.add(waypoint2);
straightpath2.add(waypoint3);
straightpath2.strokeColor = 'red';
//Find waypoint locations relative to waypoint2
var veca = new Point(waypoint1.x-waypoint2.x,waypoint2.y-waypoint1.y);
var vecb = new Point(waypoint3.x-waypoint2.x,waypoint2.y-waypoint3.y);
//Uses a circle to find intersections on each path to have
//equal length vectors being added to find angle bisector
var minpathlength = Math.min(straightpath1.length,straightpath2.length);
var bisectcirc = new Path.Circle(waypoint2, minpathlength-0.1);
var intersections1 = straightpath1.getIntersections(bisectcirc);
var intersections2 = straightpath2.getIntersections(bisectcirc);
//Creates angle bisector that fillet circle will be placed on
var intervec1 = new Point(intersections1[0].point.x-waypoint2.x,waypoint2.y-intersections1[0].point.y);
var intervec2 = new Point(intersections2[0].point.x-waypoint2.x,waypoint2.y-intersections2[0].point.y);
var addvec = intervec1 + intervec2;
var addpoint = new Point(addvec.x, addvec.y);
//Calculates slope and b intercept for this added vector
var addslope = (addpoint.y/addpoint.x);
var addbintercept = addpoint.y - (addslope*addpoint.x);
if(minpathlength == straightpath1.length){
//Finds equation for tangent line from shortest path that
//will intersect with the angle bisector
var slope = (veca.y/veca.x);
var bintercept = veca.y - (slope*veca.x);
var tangentslope = -(veca.x/veca.y);
var tangentbintercept = veca.y - (tangentslope*veca.x);
} else {
//This code runs if waypoint2 to waypoint3 is a shorter distance
var slope = (vecb.y/vecb.x);
var bintercept = vecb.y - (slope*vecb.x);
var tangentslope = -(vecb.x/vecb.y);
var tangentbintercept = vecb.y - (tangentslope*vecb.x);
}
//Finds point of intersection between the angle bisector line
//and the tangent line
if(addslope > 0){
var intersectslope = tangentslope - addslope;
} else {
var intersectslope = tangentslope + addslope;
}
if(tangentbintercept > 0){
var intersectbintercept = addbintercept - tangentbintercept;
} else {
var intersectbintercept = addbintercept + tangentbintercept;
}
//Finds x and y coordinate of the point of intersection
var intersectx = intersectbintercept/intersectslope;
var intersecty = (addslope*intersectx) + addbintercept;
//Draws circle to visualize intersection point relative to waypoint2
var intersect = new Point(intersectx+waypoint2.x, waypoint2.y-intersecty);
var intersectcirc = new Path.Circle(intersect, 5);
intersectcirc.fillColor = 'black';
//Finds dot product and cross product of original vectors to
//find a value for the angle of the vectors
var dotprod = veca.x*vecb.x+veca.y*vecb.y;
var crossprod = veca.x*vecb.y-veca.y*vecb.x;
var angle = Math.atan2(crossprod, dotprod);
//Path from waypoint2 to intersection to better visualize
var midpath = new Path();
midpath.add(waypoint2);
midpath.add(intersect);
midpath.strokeColor = 'blue';
//Bendradius is a user input to determine where the fillet
//will be located along the two vectors
var bendradius = midpath.length;
//Sets center point for circle
var filletposition = midpath.getPointAt(bendradius)
//Finds dynamic radius for circle that creates the fillet
//based on the bendradius and calculated angle
var h = (bendradius*Math.sin(angle/2));
var absh = Math.abs(h);
absh = absh + 0.2;
//Draws circle used for fillet
var fillet = new Path.Circle(filletposition,absh);
fillet.strokeColor = 'blue'
//Finds intersections between the drawn circle and the original paths
var intersections4 = straightpath1.getIntersections(fillet);
var intersections5 = straightpath2.getIntersections(fillet);
var intersections6 = midpath.getIntersections(fillet);
//Draws final fillet in three parts: waypoint1 to first intersection,
//arc of circle, intersection to waypoint3
var final1 = new Path();
final1.add(waypoint1);
final1.add(intersections4[0].point);
final1.strokeColor = 'black';
var final2 = new Path.Arc(intersections4[0].point, intersections6[0].point, intersections5[0].point);
final2.strokeColor = 'black';
var final3 = new Path();
final3.add(waypoint3);
final3.add(intersections5[0].point);
final3.strokeColor = 'black';
I was using this function, which given a latlong, it centers the google maps considering the offsetX and offsetY:
customCenter: function ( latlng, offsetx, offsety ) {
// latlng is the apparent centre-point
// offsetx is the distance you want that point to move to the right, in pixels
// offsety is the distance you want that point to move upwards, in pixels
var self = this;
var scale = Math.pow(2, self.map.getZoom());
google.maps.event.addListenerOnce(self.map,"projection_changed", function() {
var worldCoordinateCenter = self.map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x,
worldCoordinateCenter.y + pixelOffset.y
);
var newCenter = self.map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
self.map.setCenter(newCenter);
});
}
Which I'm using like, so:
self.customCenter (marker.position, $('aside').width(), 0 );
But now they'are asking me to fitbounds with the offset ( so none of the Markers are out of the view
(usually used like so:)
var bounds = new google.maps.LatLngBounds();
bounds.extend(myPlace);
bounds.extend(Item_1);
bounds.extend(Item_2);
bounds.extend(Item_3);
self.map.fitBounds(bounds);
But I don't see a way to combine this two functionalities
( this is because I have an aside floating ( pos: absolute ) over the google maps, just like this image I found online:
)
Any ideas?
-I'm trying like this now:
customCenterWithBounds: function ( offsetx, offsety ) {
// offsetx is the distance you want that point to move to the right, in pixels
// offsety is the distance you want that point to move upwards, in pixels
var self = this;
self.map.fitBounds(self.bounds);
var mapCenter = self.map.getCenter();
var latlng = new google.maps.LatLng (mapCenter.lat(), mapCenter.lng() );
var scale = Math.pow(2, self.map.getZoom());
google.maps.event.addListenerOnce(self.map,"projection_changed", function() {
var worldCoordinateCenter = self.map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x,
worldCoordinateCenter.y + pixelOffset.y
);
var newCenter = self.map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
self.map.setCenter(newCenter);
});
}
But the map would be over-zoomed.. and not even in the center of the visible area..
I ended up doing this
customCenter: function (offsetx, offsety, bounds_obj ) {
if ($(window).width() <= 1200 ) {
offsetx = 0;
}
var self = this;
google.maps.event.addListenerOnce(self.map,"projection_changed", function() {
var latlng;
var fbObj = self.map.fitBounds(self.bounds);
var mapCenter = self.map.getCenter();
latlng = new google.maps.LatLng(mapCenter.lat(), mapCenter.lng());
var scale = Math.pow(2, self.map.getZoom());
var worldCoordinateCenter = self.map.getProjection().fromLatLngToPoint(latlng);
var pixelOffset = new google.maps.Point((offsetx/scale) || 0,(offsety/scale) ||0)
var worldCoordinateNewCenter = new google.maps.Point(
worldCoordinateCenter.x - pixelOffset.x,
worldCoordinateCenter.y + pixelOffset.y
);
var newCenter = self.map.getProjection().fromPointToLatLng(worldCoordinateNewCenter);
self.map.setCenter(newCenter);
});
}
At the moment I make website for a company who wants a custom map on their page. This map is a big a vector map drawn by a graphic designer. So I use leaflet for this but I have an issue. I make it full screen. The issue is I set the bounds and everything and it works on all side except the bottom so when i start moving up and it not goes back to the bottom. The funny thing if I resize the window so for example I make it small and after back to full window size the bottom part works perfectly.
Here is my code so far:
var winh = $(window).height();
var winw = $(window).width();
var maph = $('#map').height();
var mapw = $('#map').width();
var offset = $('#map').offset();
var centerX = offset.left + mapw / 2;
var centerY = offset.top + maph / 2;
var changem = false;
var cbut = $('#building');
var southWest = new L.LatLng(winh, 0);
var northEast = new L.LatLng(0, winw);
var bounds = L.latLngBounds(southWest, northEast);
var map = L.map('map', {
maxBounds: bounds,
maxZoom: 2,
minZoom: 0,
crs: L.CRS.Simple
}).setView([winh, winw], 0);
// .setView([winh, winw], 0)
map.setMaxBounds(bounds);
// map.panTo(new L.LatLng(centerY,centerX));
// ----------------------------------
// Load the images for the map
// ----------------------------------
var imageUrl = 'http://rigailoveyou.exflairdigital.com/public/img/houses.png'
var imageUrl2 = 'http://rigailoveyou.exflairdigital.com/public/img/road.png'
var imageBounds = [[winh,0], [0,winw]];
var map1 = L.imageOverlay(imageUrl, bounds,'Riga I Love You').addTo(map);
var map2 = L.imageOverlay(imageUrl2, bounds).addTo(map);
I am want to draw a plane that has an an array of points lying on it (including the origin). The three.js library draws the plane on the origin, facing the xy plane. Right now, I am having trouble of moving the it from the origin to a position such that it contains the points.
So far, I have managed to find a way to orient some planes that lie on the y-axis:
var directionalVectors = __getDirectionVectors(points);
var normal = __getNormalOfPlane(directionalVectors);
var angleXY = __getAngleBetweenPlanes( normal, new THREE.Vector3(0, 0, 1) );
plane.rotateY(- angleXY );
plane.translateY( planeDimensions.width /2.0);
plane.translateX( planeDimensions.height /2.0);
This is how I calculate the direction vectors:
var __getDirectionVectors = function( points ){
var numOfPoints = points.length, i;
var pointOne, pointTwo, directionalVectors = [], directionalVector;
for( i = 0; i < numOfPoints - 1; i++){
pointOne = points[i];
pointTwo = points[i + 1];
directionalVector = new THREE.Vector3().subVectors(pointOne, pointTwo);
directionalVectors.push(directionalVector);
}
return directionalVectors;
};
This is how I calculate the normal:
var __getNormalOfPlane = function(vectors){
var numOfVectors = vectors.length;
var vectorOne, vectorTwo, normal;
if( numOfVectors >= 2){
vectorOne = vectors[0];
vectorTwo = vectors[1];
normal = new THREE.Vector3().crossVectors(vectorOne, vectorTwo);
}
return normal;
};
This is how I calculate the angle between the plane and the XY plane:
//http://www.netcomuk.co.uk/~jenolive/vect14.html
var __getAngleBetweenPlanes = function( normalOne, normalTwo){
var dotPdt = normalOne.dot(normalTwo);
var angle = Math.acos( dotPdt / ( normalOne.length() * normalTwo.length() ) );
return angle;
}
Is there any way I could orient the plane properly for all types of planes?
I have a small Box2D thing (using box2dweb.js), but despite setting gravity to (0,0), and no forces/impulse being imparted on any objects, the only dynamic shape I have in the scene moves when I start the draw loop. I have no idea why O_O
Would anyone know why http://pomax.nihongoresources.com/downloads/temp/box2d/physics.html has the "ball" moving after hitting start?
The relevant bits of code are:
// shortcut aliasses
var d = Box2D.Dynamics,
v = Box2D.Common.Math,
s = Box2D.Collision.Shapes;
var ball,
gravity = new v.b2Vec2(0,0);
world = new d.b2World(gravity, true);
// setup the world box
var setupWorldBox = function(worldbox) {
var fixDef = new d.b2FixtureDef;
fixDef.density = 0;
fixDef.friction = 0;
fixDef.restitution = 0;
var bodyDef = new d.b2BodyDef;
bodyDef.type = d.b2Body.b2_staticBody;
bodyDef.position.x = worldbox.width/2;
bodyDef.position.y = worldbox.height/2;
fixDef.shape = new s.b2PolygonShape;
fixDef.shape.SetAsBox(worldbox.width/2, worldbox.height/2);
world.CreateBody(bodyDef).CreateFixture(fixDef);
}
// draw loop
var drawFrame = function() {
world.Step(1/60,10,10);
world.ClearForces();
ball.update(); // only updates the ball's DOM element position
requestAnimFrame(drawFrame);
};
// start the game
function start() {
var worldParent = document.querySelector("#world");
setupWorldBox(worldParent.getBoundingClientRect());
ball = new Ball(worldParent, document.querySelector(".ball"), d,v,s, world);
drawFrame();
}
For the main body, and the following code for defining the "ball":
var Ball = function(gamediv, element, d,v,s, world) {
var pbbox = gamediv.getBoundingClientRect();
var bbox = element.getBoundingClientRect();
this.el = element;
this.width = bbox.width;
this.height = bbox.height;
var bodyDef = new d.b2BodyDef;
bodyDef.type = d.b2Body.b2_dynamicBody;
var fixDef = new d.b2FixtureDef;
fixDef.shape = new s.b2PolygonShape;
fixDef.shape.SetAsBox(bbox.width/2, bbox.height/2);
bodyDef.position.x = bbox.left - pbbox.left;
bodyDef.position.y = bbox.top - pbbox.top;
this.b2 = world.CreateBody(bodyDef);
this.b2.CreateFixture(fixDef);
};
Ball.prototype = {
el: null,
b2: null,
width: 0, height: 0,
// Box2D position for the ball
center: function() { return this.b2.GetWorldCenter(); },
// update the DOM element based on Box2D position
update: function() {
var c = this.center();
this.el.style.left = c.x + "px";
this.el.style.top = c.y + "px";
}
};
Ball.prototype.constructor = Ball;
Neither of these bits of code introduces forces, as far as I can tell, so if anyone knows why the coordinates for the ball change anyway, please let me know, so I can turn this into something useful instead of something confusing =)
It turns out my code was creating a solid object as game world, which meant Box2D was trying to perform collision resolution because the "ball" was located inside another solid object.
The solution (based on http://box2d-js.sourceforge.net but with box2dweb API calls) was this:
// setup the world box
var setupWorldBox = function(worldbox) {
var worldAABB = new Box2D.Collision.b2AABB;
worldAABB.lowerBound.Set(0,0);
worldAABB.upperBound.Set(worldbox.width, worldbox.height);
var gravity = new b2Vec2(0, 0);
var doSleep = true;
var world = new b2World(worldAABB, gravity, doSleep);
[....]
}