Google.maps panTo - google-maps-api-3

I am trying to center my map on the center of a custom infoBox. Instead, I am panning to a completely different place, far from my map markers. Here is my code:
var projection = this.getProjection(),
centerX = point.x - (125)/2,
centerY = point.y - (120)/2,
centerPoint = new google.maps.Point(centerX, centerY);
var latLngOfBoxTop = projection.fromContainerPixelToLatLng(centerPoint);
this.map.panTo(latLngOfBoxTop);
What is a better way to pan to a point on the map?

I figured this out and never updated my own question. It was easy: I simply set the center of the map to the marker's location (a latitude and longitude that I already have), and then I used "panBy", which takes pixels as its parameters, not latitude and longitude:
var latlng = this.latlng_,
div_height = $('#infobox-wrap-' + markerID).css('height').replace(/px/, ''),
infoBoxHalfHeight = 0,
halfDivHeight = Math.round(div_height/2);
infoBoxHalfHeight = halfDivHeight + 16; //accounts for the infoBox's "pointer" graphic
this.map.setCenter(latlng);
this.map.panBy(0, infoBoxHalfHeight);

Related

Draw an ellipse arc between two points in Three.js

I've been trying to draw an ellipse arc between two arbitrary points but my implementation is not working in some situations.
Because a part of this is involves mathematics, I started by asking this question.
Basically, given two points and the center, you can always get an ellipse if you allow rotation, except for cases where the points are collinear.
The solution proposed to that question is to:
Translate the center to the origin, translating both points by the same vector.
Rotate both points by the angle -alpha which is the simetric of the angle of the largest vector with the positive x-semiaxis.
Solve the ellipse equation to find its radiuses (system of two equations with two unknowns).
Define the ellipse
Rotate back the ellipse with the angle alpha and translate back to its center.
However, I'm having trouble implementing this in Three.js.
The documentation for the EllipseCurve lists the expected parameters. I assume the starting angle to always be zero and then set the end angle to either the angle between the two vectors or its simetric. I also want the arc to always be the smallest (i.e., if the angle is bigger than 180ยบ, I'd use the complementary arc). I assume the center of the ellipse to be the middle point between the centers of the shape's bounding boxes.
This is my example code:
https://jsfiddle.net/at5dc7yk/1/
This example tries to create an arc from a vertex in the original shape and the same vertex in the modified shape.
Code regarding the ellipse arc is under the class EllipseArc and you can mess with the transformation applied to the object in line 190.
It works for some cases:
But not all:
Just an idea from scratch, not the ultimate solution.
When you clone and translate object, to build an arc between two respective points you'll need their coordinates in world coordinate system, and a coordinate of the middle point between centroids of objects.
Find the mid point between points in world space (between start and end vectors).
Find its projection on the vector of translation (this is the center of an arc).
Find the angle between vectors that you get by subtraction the result center vector from each of them.
Divide an angle by amount of divisions - you'll get the step value.
Get the end vector as the base and rotate it around an axis (which is the normal of a triangle, built with start, center, end vectors) in a loop, multiplying that step angle value with the number of the current iteration.
Code example:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 10000);
camera.position.set(0, 0, 150);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var shapeGeom = new THREE.ShapeBufferGeometry(new THREE.Shape(californiaPts));
shapeGeom.center();
shapeGeom.scale(0.1, 0.1, 0.1);
var shapeMat = new THREE.MeshBasicMaterial({
color: "orange"
});
var shape = new THREE.Mesh(shapeGeom, shapeMat);
shape.updateMatrixWorld();
scene.add(shape);
var shapeClone = shape.clone();
shapeClone.position.set(25, 25, 0);
shapeClone.updateMatrixWorld();
scene.add(shapeClone);
var center = new THREE.Vector3().lerpVectors(shapeClone.position, shape.position, 0.5);
var vecStart = new THREE.Vector3();
var vecEnd = new THREE.Vector3();
var pos = shapeGeom.getAttribute("position");
for (let i = 0; i < pos.count; i++) {
vecStart.fromBufferAttribute(pos, i);
shape.localToWorld(vecStart);
vecEnd.fromBufferAttribute(pos, i);
shapeClone.localToWorld(vecEnd);
makeArc(center, vecStart, vecEnd);
}
function makeArc(center, start, end) {
console.log(center, start, end);
let vM = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
let dir = new THREE.Vector3().subVectors(end, start).normalize();
let c = new THREE.Vector3().subVectors(vM, center);
let d = c.dot(dir);
c.copy(dir).multiplyScalar(d).add(center); // get a center of an arc
let vS = new THREE.Vector3().subVectors(start, c);
let vE = new THREE.Vector3().subVectors(end, c);
let a = vS.angleTo(vE); // andgle between start and end, relatively to the new center
let divisions = 100;
let aStep = a / divisions;
let pts = [];
let vecTemp = new THREE.Vector3();
let tri = new THREE.Triangle(start, c, end);
let axis = new THREE.Vector3();
tri.getNormal(axis); // get the axis to rotate around
for (let i = 0; i <= divisions; i++) {
vecTemp.copy(vE);
vecTemp.applyAxisAngle(axis, aStep * i);
pts.push(vecTemp.clone());
}
let g = new THREE.BufferGeometry().setFromPoints(pts);
let m = new THREE.LineDashedMaterial({
color: 0xff0000,
dashSize: 1,
gapSize: 1
});
let l = new THREE.Line(g, m);
l.computeLineDistances();
l.position.copy(c);
scene.add(l);
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script>
var californiaPts = [
new THREE.Vector2(610, 320),
new THREE.Vector2(450, 300),
new THREE.Vector2(392, 392),
new THREE.Vector2(266, 438),
new THREE.Vector2(190, 570),
new THREE.Vector2(190, 600),
new THREE.Vector2(160, 620),
new THREE.Vector2(160, 650),
new THREE.Vector2(180, 640),
new THREE.Vector2(165, 680),
new THREE.Vector2(150, 670),
new THREE.Vector2(90, 737),
new THREE.Vector2(80, 795),
new THREE.Vector2(50, 835),
new THREE.Vector2(64, 870),
new THREE.Vector2(60, 945),
new THREE.Vector2(300, 945),
new THREE.Vector2(300, 743),
new THREE.Vector2(600, 473),
new THREE.Vector2(626, 425),
new THREE.Vector2(600, 370),
new THREE.Vector2(610, 320)
];
</script>
If you don't translate, and just rotate an object, in this case you don't need to compute a new center for each arc, just omit that step, as all the centers are equal to the centroid of the object.
I hope I explained it in more or less understandable way ^^

D3.geo : responsive frame given a geojson object?

I use Mike Bostock's code to Center a map in d3 given a geoJSON object.
The important part of the code is this:
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("/d/4090846/us.json", function(error, us) {
var states = topojson.feature(us, us.objects.states),
state = states.features.filter(function(d) { return d.id === 34; })[0];
/* ******************* AUTOCENTERING ************************* */
// Create a unit projection.
var projection = d3.geo.albers()
.scale(1)
.translate([0, 0]);
// Create a path generator.
var path = d3.geo.path()
.projection(projection);
// Compute the bounds of a feature of interest, then derive scale & translate.
var b = path.bounds(state),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
// Update the projection to use computed scale & translate.
projection
.scale(s)
.translate(t);
/* ******************* END *********************************** */
// Landmass
svg.append("path")
.datum(states)
.attr("class", "feature")
.attr("d", path);
// Focus
svg.append("path")
.datum(state)
.attr("class", "outline")
.attr("d", path);
});
For example, bl.ocks.org/4707858 zoom in such:
How to center and zoom on the target topo/geo.json AND adjust the svg frame dimensions so it fit a 5% margin on each size ?
Mike's explained
Basically, Mike's code states the frame dimensions via
var width = 960, height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
Once the frame is hardly set, then you check out the largest limiting ratio so your geojson shape fill your svg frame on its largest dimension relative to the svg frame dimensions widht & height. Aka, if the shape's width VS frame width or shape height VS frame height is the highest. This, in turn, help to recalculate the scale via 1/highest ratio so the shape is as small as required. It's all done via:
var b = path.bounds(state),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
// b as [[left, bottom], [right, top]]
// (b[1][0] - b[0][0]) = b.left - b.right = shape's width
// (b[1][3] - b[0][4]) = b.top - b.bottom = shape's height
Then, refreshing your scale and transition you get Mike Bostock's zoom:
New framing
To frame up around the geojson shape is actually a simplification of Mike's code. First, set temporary svg dimensions:
var width = 200;
var svg = d3.select("body").append("svg")
.attr("width", width);
Then, get the dimensions of the shapes and compute around it :
var b = path.bounds(state);
// b.s = b[0][1]; b.n = b[1][1]; b.w = b[0][0]; b.e = b[1][0];
b.height = Math.abs(b[1][1] - b[0][1]); b.width = Math.abs(b[1][0] - b[0][0]);
var r = ( b.height / b.width );
var s = 0.9 / (b.width / width); // dimension of reference: `width` (constant)
//var s = 1 / Math.max(b.width / width, b.height / height ); // dimension of reference: largest side.
var t = [(width - s * (b[1][0] + b[0][0])) / 2, (width*r - s * (b[1][1] + b[0][1])) / 2]; //translation
Refresh projection and svg's height:
var proj = projection
.scale(s)
.translate(t);
svg.attr("height", width*r);
It's done and fit the pre-allocated width=150px, find the needed height, and zoom properly. See http://bl.ocks.org/hugolpz/9643738d5f79c7b594d0

Draw (inverted) geometric circle in google maps api v3

I'm looking for a way to draw an inverted geometric (not geographical) circle in google maps api v3.
Essentially the goal is to dim the map except around a map object - as a way to make the map object stand out. To do this, I have employed an inverted overlay and have a method to create the circle "hole" in my "shadow-overlay".
However the method I've employed to get the lat/lng coordinates to generate this circle is adjusted to the Mercator projection and is not a consistent size or shape because it is relative to it's position from the equator. The method needs to create a circle (without using google's circle object - or using it with a way to extract it's path) that will calculate the lat/lng points from a center, based on a radius field that doesn't take the Mercator projection into account - such that it will display a perfect circle anywhere it is drawn on the map.
It shouldnt be hard, but I'm struggling to convert this function to NOT apply the Mercator projection into the result:
function getCircleCoords(point, radius) {
var d2r = Math.PI / 180; // degrees to radians
var points = 90;
var circleLatLngs = new Array();
var circleLat = radius * 0.621371192 * 0.014483;
var circleLng = circleLat / Math.cos( point.lat() * d2r);
for (var i = 0; i < points+1; i++) {
var theta = Math.PI * (i / (points / 2));
var vertexLat = point.lat() + (circleLat * Math.sin(theta));
var vertexLng = point.lng() + (circleLng * Math.cos(theta));
var vertexLat = point.lat() + (circleLat * (theta));
var vertexLng = point.lng() + (circleLng * (theta));
var vertextLatLng = new google.maps.LatLng(vertexLat, vertexLng);
circleLatLngs.push( vertextLatLng );
}
return circleLatLngs;
}
This would then get called like:
feature = new google.maps.Polygon({
paths: [[my_shadow_layer_path],[getCircleCoords(latLng_, 800)] ],
fillColor: '#ff0000',
fillOpacity: 0.5,
map: map_
});
}
Thoughts?
To use a Polygon, you need to calculate the circle coordinates in pixel (world) coordinates translate them to geographic coordinates using fromPointToLatLng to get the appropriate coordinates for the circle. Or use an overlay of the correct size and handle zoom changes yourself.
You can try this library https://github.com/raihan2006i/google-map-inverted-circle. which works on Google Map V3. See below for example code blocks
var iCircle;
var map;
function initialize() {
var mapDiv = document.getElementById('gMap');
map = new google.maps.Map(mapDiv, {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
iCircle = new InvertedCircle({
center: map.getCenter(),
map: map,
radius: 5000, // 5 km
editable: true,
stroke_weight: 5,
fill_opacity: 0.5,
fill_color: "#ff0000"
});
}
google.maps.event.addDomListener(window, 'load', initialize);

Create 3rd person camera position calculation with quaternions

I want to create a 3rd person camera similiar to example. The camera should stick behind the object and rotate if the rotation difference between camera and object is too high (maybe above ten percent).
This is my actual camera code:
var targetPosition = this.getTargetPosition();
var targetRotation = this.getTargetRotation();
var tmpQuaternion = new THREE.Quaternion();
tmpQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 180 * (Math['PI'] / 180));
this.camera.quaternion = targetRotation;
this.camera.position = targetPosition;
this.camera.quaternion.multiplySelf(tmpQuaternion);
this.camera.quaternion.normalize();
this.camera.updateMatrix();
this.camera.translateZ(200);
this.camera.translateY(50);
But there are several problems right now. The camera quaternion should not set directly to the target rotation. But I dont know how to calculate the difference between camera quaternion and target quaternion and use maybe this if the distance is too high:
var qm = new THREE.Quaternion();
THREE.Quaternion.slerp(targetRotation, this.camera.quaternion, qm, time);
this.camera.quaternion = qm;
The second problem is the position itself. Currently I set camera position to the object position and translate it back to view behind, but the translation should be already in target position and the camera position should be translated to the target position.
Update 1: I made an example html: http://ssachtleben.github.com/CameraProblem/
Update 2: I made some progress now. Seems like I get quaternion difference with this function:
getAxisAngle = function(quaternion1, quaternion2) {
var tmpQuaternion = new THREE.Quaternion();
tmpQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 180 * (Math['PI'] / 180));
var tmpRotation1 = quaternion1.clone();
tmpRotation1.multiplySelf(tmpQuaternion);
tmpRotation1.normalize();
var tmpRotation2 = quaternion2.clone();
if (tmpRotation2.w > 1) {
tmpRotation2.normalize();
}
var angle1 = 2 * Math['acos'](tmpRotation1.w);
var angle2 = 2 * Math['acos'](tmpRotation2.w);
var diff = angle1 > angle2 ? angle1 - angle2 : angle2 - angle1;
return diff;
};
But know I need to freeze the axis if the angle difference is too high. How can I do this?
Any help would be appreciated.
Ok finally the camera is fixed and works as excepted:
var targetPosition = this.getTargetPosition();
var targetRotation = this.getTargetRotation();
var tmpQuaternion = new THREE.Quaternion();
tmpQuaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), 180 * (Math['PI'] / 180));
targetRotation.multiplySelf(tmpQuaternion);
targetRotation.quaternion.normalize();
var qm = new THREE.Quaternion();
THREE.Quaternion.slerp(this.camera.quaternion, targetRotation, qm, 0.07);
this.camera.quaternion = qm;
this.camera.quaternion.normalize();

Draw a circle with google maps api3 that doesn't resize

With google maps api2 I was drawing a circle using this code:
var markerPoint = currentMarker.getPoint();
var polyPoints = Array();
var mapNormalProj = G_NORMAL_MAP.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = mapNormalProj.fromLatLngToPixel(markerPoint, mapZoom);
var polySmallRadius = 20;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var polyRadius = polySmallRadius;
var pixelX = clickedPixel.x + 5 + polyRadius * Math.cos(aRad);
var pixelY = clickedPixel.y - 10 + polyRadius * Math.sin(aRad);
var polyPixel = new GPoint(pixelX,pixelY);
var polyPoint = mapNormalProj.fromPixelToLatLng(polyPixel,mapZoom);
polyPoints.push(polyPoint);
}
// Using GPolygon(points, strokeColor?, strokeWeight?, strokeOpacity?, fillColor?, fillOpacity?)
highlightCircle = new GPolygon(polyPoints,"#000000",2,0.0,"#FF0000",.5);
map.addOverlay(highlightCircle);
I've managed to transform this code to api3:
var markerPoint = currentMarker.getPosition();
var polyPoints = Array();
var mapNormalProj = map.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = mapNormalProj.fromLatLngToPoint(markerPoint);
var polyRadius = 20;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var pixelX = clickedPixel.x + 5 + (polyRadius * Math.cos(aRad));
var pixelY = clickedPixel.y - 10 + (polyRadius * Math.sin(aRad));
var polyPixel = new google.maps.Point(pixelX,pixelY);
var polyPoint = mapNormalProj.fromPointToLatLng(polyPixel);
polyPoints.push(polyPoint);
}
highlightCircle = new google.maps.Polygon({
paths: polyPoints,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
highlightCircle.setMap(map);
If you look more closely at the api3 example, the mapZoom variable is not used anywhere.
In api2, the code generates a small circle around my marker - around 35px radius. When I zoom into the map, the radius stays at 35px (because the zoom is taken into account).
With api3 on the other hand, I have a huge circle - more than 200px wide and when I zoom in, the circle becomes bigger and bigger.
It behaves the same way as the circle object available in api3.
What I want is just a small circle around my marker, that is not 100km in diameter, but just a few pixels around my marker (this circle acts like a hover element in html).
Any ideas how to achieve that?
You might have better luck using custom marker, not a circle. See "Vector Icon" from the documentation here: https://developers.google.com/maps/documentation/javascript/overlays#Icons
var marker = new google.maps.Marker({
position: new google.maps.LatLng(-25.363882, 131.044922),
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10
},
draggable: true,
map: map
});
You're doing the calculation based on the Point plane which plane remains the same no matter what zoom level you are at. You probably mean to do the calculation using pixels.
The methods you are looking for are here. fromLatLngToContainerPixel and fromContainerPixelToLatLng or fromLatLngToDivPixel and fromDivPixelToLatLng.
This means you should probably wrap up that code into an OverlayView and call getProjection() on your map to get the projection and then use one set of those methods to do the calculation.

Resources