I am trying to create a polygon around an existing polyline. I thought of drawing polylines parallel to the existing polyline and then joining them to create a polygon. I tried unsuccessfully to do the math to draw the parallel lines. I found this link which I used to create the polylines on either sides.
http://wtp2.appspot.com/ParallelLines.htm
It seemed exactly what I was looking for. I started the conversion from v2 to v3. I tried to keep to minimal code and deleted the rest. I have also removed the listener for change in zoom level which was present in the original code.
It worked perfectly when I used a small fixed polyline. However when I increased the size of the polyline the parallel polylines began to go haywire.
The code that I have is :
var points = null;
var map;
var line1;
var line2;
var prj = null;
var idlelistener;
var gapPx = 2;
var weight = 4;
function BDCCParallelLines(maps, point, bounds) {
map = maps;
points = point;
//map.fitBounds(bounds);
MyOverlay.prototype = new google.maps.OverlayView();
MyOverlay.prototype.onAdd = function() { }
MyOverlay.prototype.onRemove = function() { }
MyOverlay.prototype.draw = function() { }
function MyOverlay(map) { this.setMap(map); }
var overlay = new MyOverlay(map);
// Wait for idle map
idlelistener = google.maps.event.addListener(map, 'idle', function() {
// Get projection
prj = overlay.getProjection();
recalc();
})
}
function recalc() {
google.maps.event.removeListener(idlelistener);
var zoom = this.map.getZoom();
//left and right swapped throughout!
var pts1 = new google.maps.MVCArray();//left side of center
var pts2 = new google.maps.MVCArray();//right side of center
//shift the pts array away from the centre-line by half the gap + half the line width
var o = (this.gapPx + this.weight)/2;
var p2l,p2r;
for (var i=1; i<this.points.length; i+=2){
var p1lm1;
var p1rm1;
var p2lm1;
var p2rm1;
var thetam1;
var p1 = this.prj.fromLatLngToContainerPixel(this.points.getAt(i-1),zoom) //**fromLatLngToPixel
var p2 = this.prj.fromLatLngToContainerPixel(this.points.getAt(i),zoom) //**fromLatLngToPixel
var theta = Math.atan2(p1.x-p2.x,p1.y-p2.y) + (Math.PI/2);
var dl = Math.sqrt(((p1.x-p2.x)*(p1.x-p2.x))+((p1.y-p2.y)*(p1.y-p2.y)));
if(theta > Math.PI)
theta -= Math.PI*2;
var dx = Math.round(o * Math.sin(theta));
var dy = Math.round(o * Math.cos(theta));
var p1l = new google.maps.Point(p1.x+dx,p1.y+dy); //GPoint
var p1r = new google.maps.Point(p1.x-dx,p1.y-dy);
p2l = new google.maps.Point(p2.x+dx,p2.y+dy);
p2r = new google.maps.Point(p2.x-dx,p2.y-dy);
if(i==1){ //first point
pts1.push(this.prj.fromContainerPixelToLatLng(p1l),zoom); //**fromPixelToLatLng
pts2.push(this.prj.fromContainerPixelToLatLng(p1r),zoom); //**fromPixelToLatLng
}
else{ // mid points
if(theta == thetam1){
// adjacent segments in a straight line
pts1.push(this.prj.fromContainerPixelToLatLng(p1l),zoom);
pts2.push(this.prj.fromContainerPixelToLatLng(p1r),zoom);
}
else{
var pli = this.intersect(p1lm1,p2lm1,p1l,p2l);
var pri = this.intersect(p1rm1,p2rm1,p1r,p2r);
var dlxi = (pli.x-p1.x);
var dlyi = (pli.y-p1.y);
var drxi = (pri.x-p1.x);
var dryi = (pri.y-p1.y);
var di = Math.sqrt((drxi*drxi)+(dryi*dryi));
var s = o / di;
var dTheta = theta - thetam1;
if(dTheta < (Math.PI*2))
dTheta += Math.PI*2;
if(dTheta > (Math.PI*2))
dTheta -= Math.PI*2;
if(dTheta < Math.PI){
//intersect point on outside bend
pts1.push(this.prj.fromContainerPixelToLatLng(p2lm1),zoom);
pts1.push(this.prj.fromContainerPixelToLatLng(new google.maps.Point(p1.x+(s*dlxi),p1.y+(s*dlyi))),zoom);
pts1.push(this.prj.fromContainerPixelToLatLng(p1l));
}
else if (di < dl){
pts1.push(this.prj.fromContainerPixelToLatLng(pli),zoom);
}
else{
pts1.push(this.prj.fromContainerPixelToLatLng(p2lm1),zoom);
pts1.push(this.prj.fromContainerPixelToLatLng(p1l),zoom);
}
dxi = (pri.x-p1.x)*(pri.x-p1.x);
dyi = (pri.y-p1.y)*(pri.y-p1.y);
if(dTheta > Math.PI){
//intersect point on outside bend
pts2.push(this.prj.fromContainerPixelToLatLng(p2rm1),zoom);
pts2.push(this.prj.fromContainerPixelToLatLng(new google.maps.Point(p1.x+(s*drxi),p1.y+(s*dryi))),zoom);
pts2.push(this.prj.fromContainerPixelToLatLng(p1r),zoom);
}
else if(di<dl)
pts2.push(this.prj.fromContainerPixelToLatLng(pri),zoom);
else{
pts2.push(this.prj.fromContainerPixelToLatLng(p2rm1),zoom);
pts2.push(this.prj.fromContainerPixelToLatLng(p1r),zoom);
}
}
}
p1lm1 = p1l;
p1rm1 = p1r;
p2lm1 = p2l;
p2rm1 = p2r;
thetam1 = theta;
}
pts1.push(this.prj.fromContainerPixelToLatLng(p2l),zoom);//final point
pts2.push(this.prj.fromContainerPixelToLatLng(p2r),zoom);
this.line1 = new google.maps.Polyline({
map: map,
path: pts1,
strokeColor: "#0000FF",
strokeWeight: 4,
strokeOpacity: 1.0
});
this.line2 = new google.maps.Polyline({
map: map,
path: pts2,
strokeColor: "#0000FF",
strokeWeight: 4,
strokeOpacity: 1.0
});*/
createPolygon(pts1,pts2);
}
function intersect(p0,p1,p2,p3)
{
// this function computes the intersection of the sent lines p0-p1 and p2-p3
// and returns the intersection point,
var a1,b1,c1, // constants of linear equations
a2,b2,c2,
det_inv, // the inverse of the determinant of the coefficient matrix
m1,m2; // the slopes of each line
var x0 = p0.x;
var y0 = p0.y;
var x1 = p1.x;
var y1 = p1.y;
var x2 = p2.x;
var y2 = p2.y;
var x3 = p3.x;
var y3 = p3.y;
// compute slopes, note the cludge for infinity, however, this will
// be close enough
if ((x1-x0)!=0)
m1 = (y1-y0)/(x1-x0);
else
m1 = 1e+10; // close enough to infinity
if ((x3-x2)!=0)
m2 = (y3-y2)/(x3-x2);
else
m2 = 1e+10; // close enough to infinity
// compute constants
a1 = m1;
a2 = m2;
b1 = -1;
b2 = -1;
c1 = (y0-m1*x0);
c2 = (y2-m2*x2);
// compute the inverse of the determinate
det_inv = 1/(a1*b2 - a2*b1);
// use Kramers rule to compute xi and yi
var xi=((b1*c2 - b2*c1)*det_inv);
var yi=((a2*c1 - a1*c2)*det_inv);
return new google.maps.Point(Math.round(xi),Math.round(yi)); // ** CHANGED HERE
}
function createPolygon(side1,side2){
var a = new Array();
for(var i = 0; i < side1.length;i++){
a.push(side1.getAt(i))
}
for(var i = side1.length-1; i >=0;i--){
a.push(side2.getAt(i));
}
drawPolylinePolygon(a)
}
function drawPolylinePolygon(a){
a.push(a[0]);
var color = getColor(false);
var polygon_options = {
paths: a,
strokeColor: color,
strokeOpacity: 0.7,
strokeWeight: 2,
fillColor: color,
fillOpacity: 0.2
};
current_polygon = new google.maps.Polygon(polygon_options);
current_polygon.setMap(map);
}
The createPolygon() function is used to merge the two polylines to create a polygon.
This is the html page :
<?xml version="1.0" encoding="utf-8"?>
<!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" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title></title>
<script src="http://maps.google.com/maps/api/js?sensor=true&libraries=drawing,geometry" type="text/javascript"></script>
<script src="BDCCParallelLines.js" type="text/javascript"></script>
<script type="text/javascript">
//<![CDATA[
var map;
function linesMap(){
var latlng1 = new google.maps.LatLng(51.42, -0.95);
var mapOptions = {zoom: 22, center:latlng1, mapTypeId: google.maps.MapTypeId.ROADMAP, mapTypeControl: false};
var map = new google.maps.Map(document.getElementById('mapLines'),mapOptions);
var pts = new Array();
var latlngbounds = new google.maps.LatLngBounds();
pts.push (new google.maps.LatLng(51.42, -0.97));
latlngbounds.extend(new google.maps.LatLng(51.42, -0.97));
pts.push (new google.maps.LatLng(51.43, -0.96));
latlngbounds.extend(new google.maps.LatLng(51.43, -0.96));
pts.push (new google.maps.LatLng(51.425, -0.955));
latlngbounds.extend(new google.maps.LatLng(51.425, -0.955));
pts.push (new google.maps.LatLng(51.42, -0.95));//straight at zoom = 13
latlngbounds.extend(new google.maps.LatLng(51.42, -0.95));
pts.push (new google.maps.LatLng(51.43, -0.94));
latlngbounds.extend(new google.maps.LatLng(51.43, -0.94));
pts.push (new google.maps.LatLng(51.43, -0.9375));//horz & straight
latlngbounds.extend(new google.maps.LatLng(51.43, -0.9375));
pts.push (new google.maps.LatLng(51.43, -0.935));
latlngbounds.extend(new google.maps.LatLng(51.43, -0.935));
pts.push (new google.maps.LatLng(51.425, -0.935));
latlngbounds.extend(new google.maps.LatLng(51.425, -0.935));
pts.push (new google.maps.LatLng(51.42, -0.935));//vert & straight
latlngbounds.extend(new google.maps.LatLng(51.42, -0.935));
var poly = new BDCCParallelLines(map,pts,latlngbounds);
var poly2 = new google.maps.Polyline({
map: map,
path: pts,
strokeColor: "#FF0000",
strokeWeight: 2,
strokeOpacity: 1.0
});
}
//]]>
</script>
</head>
<body onload="linesMap();"
style="font-weight: bold; font-size: large; font-family: Arial; background-color: #cccc99">
<div id="mapLines" style="width: 800px; height: 600px">
</div>
</body>
</html>
After searching I came across this article where Ben seems to have the same problem. The image on the link shows the exact same problem I'm having.
Google maps api parallel path lines
I would like to know if there is any way that I can improve on the existing code for the parallel polylines or if there is any other way the end result I am looking for is a polygon around the polyline.
You should use a Buffer-function that exists in any spatial-API or database, one example are
sharpmap.
Related
I have a list of Vectors which represent points on three different floors in three.js.
I am trying to group these vectors according to the floor they belong to. Is there a good formula to do this? Perhaps find height from on vector or something. Not sure how to go about this. Any help would be appreciated.
Thanks,
Rob
As Wilt said... Can't really help you without more info.
Still, if your floors are even and all stand on the xz plane (in my example), You may indeed check the points' height (position.y) against the floors'.
var container, renderer, scene, camera, controls;
var floors = [];
var points = [], materials = [], heights = [];
init();
animate();
function init() {
// renderer
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
container = document.createElement('div');
document.body.appendChild(container);
container.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera + controls
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
camera.position.set(0, 50, 750);
camera.lookAt(scene.position);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.autoRotate = true;
//floors
for(i=0; i<3; i++) {
var planeGeometry = new THREE.BoxGeometry(500, 500, 10, 10);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff * Math.random(),
side: THREE.DoubleSide,
transparent: true,
opacity : 0.3,
depthWrite : false //get rid of coplanar glitches wall/floor
});
materials.push(planeMaterial);
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = Math.PI / 2;
//plane.rotation.y = Math.PI / 8; //Uncomment to see this doesn't work if the floors move, i.e. changing rotation/position. If this is what you need, just raycast from point to floor in the animation loop and count how many floors the ray goes through (intersects.length)
plane.position.y = 75*i;
heights.push(plane.position.y);
floors.push(plane);
scene.add(plane);
}
//wall
var height = heights[2];
var planeGeometry = new THREE.BoxGeometry(500, height+100, 10, 10);
var planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff * Math.random(),
side: THREE.DoubleSide,
transparent: true,
opacity:0.3,
depthWrite : false //get rid of coplanar glitches wall/floor
});
materials.push(planeMaterial);
var plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.position.y = heights[1]+45;
plane.position.z = -510/2;
scene.add(plane);
// points
for (i=0; i<200; i++) {
var sphereGeometry = new THREE.SphereGeometry(3, 32, 16);
var sphere = new THREE.Mesh(sphereGeometry);
sphere.position.x = Math.random() * 500 - 250;
sphere.position.y = Math.random() * 300 - 100;
sphere.position.z = Math.random() * 500 - 250;
scene.add(sphere);
points.push(sphere);
}
// events
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function addSpheres() {
//addSpheres
for (i=0;i<200;i++) {
var that = points[i].position.y;
points[i].position.y = ( that < heights[0] ) ? 200 : that - 0.5;
if ( that > heights[0] && that < heights[1] ) points[i].material = materials[0];
if ( that < heights[2] && that > heights[1] ) points[i].material = materials[1];
if ( that > heights[2] ) points[i].material = materials[2];
points[i].material.needsUpdate = true;
}
}
function animate() {
controls.update();
addSpheres();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
Now, feel free to "group" these points according to your needs.
Please note that "vectors" are different from "points". Read more about the difference.
Raycasting would be the way to go if you had a more complex scene (moving floors/different planes, points moving in different directions).
I am trying to animate a line and its normal ( another line ). But when i change position after or before setting rotation of the normal strange animation occurs.
Is there anybody who has an idea on that?
I have this code in sketchpad:
http://sketch.paperjs.org/#S/jVJLb5tAEP4rKy7BqgvUbS+OcogstT2klaUeeohzIDA2q+AZtAyOI8v/vbM7mFDLUcqJne8xz0OE+RaiefT7CbioomlUUOnfu9wZ6hjcD3NjZll2vcIh9EdCn4fQxlHXSATh2Xz3//GkR9rGIvTIMucqPuzn2dS8zLOjp6x4xYGS5GU5YJo0/XKZ8lELeCWe0Vp29AQLqslJ4isH5VWPa0m4HNcTtIjLc9lj3YHXCeLzBj5Z5FgqzCaTC8BXRYJfmgrc2B2xz7VMHqnDsk2YmjtYc6BoQWFy3mhR2bp0gPF96GIqqgfvpYSGWsuWUNxkArIL373M/6hWqJ3VVAhBp7ABvqMi96Jbjj/NstNGkNw2r8e8XyHyyuoHMsopxvKUJnUgjjhniNUpyXFTg+p2Fp4Twm9ODkpk6w4L7xDDDpAn5rBCM/r6GXC4E4tdK5KfspNEHipJ2IrRab3KLOgfrjwvc9PULCqpDQxXYPZmaIfWIdLCZisyc+pLVf0JKdbezx607+TFfLgZUr/L3nv2GXfcw7uLGo/pP5c2JDnp3l7h2D1c6lsLFcdjdPwL
var outerH = 200;
var outerW = 300;
var group = new Group();
var spine = new Path({x:0, y:0});
spine.add({x:0, y:outerH/4});
spine.add({x:-outerW, y:outerH});
spine.strokeColor = 'red';
var nP = new Path();
nP.strokeColor = 'blue';
nP.add(new Point(0, 0))
nP.add(new Point(50, 0));
//nP.pivot = nP.bounds.topLeft;
group.addChildren([spine, nP]);
group.position = {x:200, y:300};
var loc = spine.getLocationAt(120);
var normal = spine.getNormalAt(120);
nP.position = loc.point;
nP.rotate(normal.angle);
view.onFrame = function(event) {
var sinus = Math.sin(event.time );
var cosinus = Math.cos(event.time );
// Change the x position of the segment point;
spine.segments[2].point.y += cosinus ;
spine.segments[2].point.x += sinus ;
var loc = spine.getLocationAt(120);
var normal = spine.getNormalAt(120);
nP.position = loc.point;
//nP.rotate(normal.angle);
}
If I uncomment -> nP.rotate(normal.angle); nP is not rotating with the line normal point?
Please read the following post on the mailing list that explains this behavior and offers an option to switch paper.js into a mode that simplifies this scenario:
https://groups.google.com/forum/#!searchin/paperjs/applymatrix/paperjs/4EIRSGzcaUI/seKoNT-PSpwJ
The problem is that in paper.js when you rotate an item and move it, the new rotation is accepted as 0 degree. So I am keeping an oldAngle variable outside the onFrame event. Than do the necessary math in onFrame to calculate the right rotation...
The right code:
var group = new Group();
var spine = new Path({x:0, y:0});
spine.add({x:0, y:50});
spine.add({x:-300, y:200});
spine.strokeColor = 'red';
var nP = new Path();
nP.strokeColor = 'blue';
nP.add(new Point(0, 0))
nP.add(new Point(50, 0));
group.addChildren([spine, nP]);
group.position = {x:200, y:300};
var loc = spine.getLocationAt(120);
var normal = spine.getNormalAt(120);
nP.position = loc.point;
nP.rotation = normal.angle;
var oldAngle = normal.angle; // keep the old rotation angle in this variable
view.onFrame = function(event) {
var sinus = Math.sin(event.time );
spine.segments[2].point.y += sinus ;
spine.segments[2].point.x += sinus ;
var loc = spine.getLocationAt(120);
var normal = spine.getNormalAt(120);
nP.position = loc.point;
nP.rotation += normal.angle - oldAngle; // here we do the necessary math
oldAngle = normal.angle;
}
I'm a social science person increasingly getting into web programming for data vis work so apologies if this question is dumb. I'm working on a polymaps implementation to visualize country level data over time. I reads in json temporal data and a geojson world map and spits out a quantile chloropleth map that iterates over monthly entries. The heart of this is a country formating function that binds a colorbrewer class to the country geojson objects (see below). This works find for the animation portion. The problem is that I am using a custom d3 layer that displays the date of the data currently displayed and acts as a mouseover control to stop the animation and choose a date or to choose a date once the animation is through. It does this by creating an blank svg element that uses the d3.scale() function to round mouse input to an integer that matches the index of the month desired. I've front loaded all the other calculations on load so that the only thing that happens at mouse over is the change of svg class (this is basically the same as Tom Carden's wealth of nations implementation on Bostock's d3 page here). Unfortunately, this still overloads the browser pretty quickly. Is there another way to do this that I'm totally missing? I admit im new to geojson so maybe some way to construct an array of classes with in the class attribute of the geojson object? Thanks a ton of any help.
function foo(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
n$(geojson[x].element)
.attr("class", geojson[x].data.formats[local])
.add("svg:title");
}
}
EDIT: I'm adding the full script below.
<meta charset="utf-8">
<script src="scripts/d3.v3.js"></script>
<script src="scripts/polymaps.js"></script>
<script src="scripts/nns.js"></script>
<script>
//Polymaps namespace
var po = org.polymaps;
//Chart dimensions
var margin = {top: 20, right: 20, bottom: 20, left: 20};
var w = 960 - margin.right;
var h = 500 - margin.top - margin.bottom;
// Create the map object, add it to #map div
var map = po.map()
.container(d3.select("#map").append("svg:svg").attr("width", w + margin.left + margin.right).attr("height",h +margin.top + margin.bottom).node())
.center({lat: 28, lon: 0})
.zoom(1.85)
.zoomRange([1.5, 4.5])
.add(po.interact());
// Add the CloudMade image tiles as a base layer…
map.add(po.image()
.url(po.url("http://{S}tile.cloudmade.com"
+ "/1a1b06b230af4efdbb989ea99e9841af" // http://cloudmade.com/register
+ "/20760/256/{Z}/{X}/{Y}.png")
.hosts(["a.", "b.", "c.", ""])));
//Import contribution data
d3.json("assets/contributionsTCC1990-1991.json", function(data){
//find length of json data object and loop over it at interval
var dataLength = Object.keys(data).length;
//Create date key/value array using construtor
function date_array_constructor() {
var dateArray = {};
for(var i = 0; i < dataLength; i++) {
var d = i + 1;
dateArray[d] = data[i].date;
}
return dateArray;
}
var dateArray = date_array_constructor();
// Insert date label/control layer and add SVG elements that take on attributes determined by load function
var labelLayer = d3.select("#map svg").insert("svg:g");
map.add(po.geoJson()
.url("assets/world.json")
.tile(false)
.zoom(3)
.on("load", load));
map.container().setAttribute("class", "Blues");
map.add(po.compass()
.pan("none"));
function find_max(data, dataLength) {
var max = 0;
for(var i in data) {
if(data[i] > max) {
max = data[i] + 1;
}
}
return max;
}
function max_array_constructor(data, dataLength) {
var maxArray = {};
for(var i=0;i<dataLength;i++) {
var d = i+1;
maxArray[d] = find_max(data[i].contributions);
}
return maxArray;
}
var maxArray = max_array_constructor(data, dataLength);
function contribution_array_constructor(data, dataLength, tccName, feature) {
var contributions = {};
//iterate over date entries
for(var i=0;i<dataLength;i++) {
//contribution iterator
contributions[i+1] = 0;
for(x in data[i].contributions){
if(x == tccName) {
contributions[i+1] = data[i].contributions[x];
}
}
}
return contributions;
}
function format_array_constructor(data, dataLength, maxArray, feature) {
var formats = {};
// console.log(feature.data.contributions);
//iterate over date entries
for(var i=0;i<dataLength;i++) {
var percentile = feature.data.contributions[i+1] / maxArray[i+1];
if(percentile != 0){
var v = "q" + ((~~(percentile*7)) + 2) + "-" + 9;
}else{
var v = "countries";
}
formats[i+1] = v;
}
return formats;
}
///////////////////////////////
//load function
///////////////////////////////
function load(e) {
//Bind geojson and json
var geojson = e.features;
console.log(geojson);
geojson.dates = dateArray;
for(var x = 0; x < geojson.length; x++) {
// var tccID = geojson[x].data.id;
var tccName = geojson[x].data.properties.name;
geojson[x].data.contributions = contribution_array_constructor(data, dataLength, tccName, geojson[x]);
geojson[x].data.formats = format_array_constructor(data, dataLength, maxArray, geojson[x]);
}
//Insert date label
var dateLabel = labelLayer.append("text")
.attr("class", "date label")
.attr("text-anchor", "end")
.attr("x", w-670)
.attr("y", h )
.text(dateArray[1]);
//Add interactive overlay for date label
var box = dateLabel.node().getBBox();
var overlay = labelLayer.append("rect")
.attr("class", "overlay")
.attr("x", box.x)
.attr("y", box.y)
.attr("opacity",0)
.attr("width", box.width)
.attr("height", box.height)
.on("mouseover",enable_interaction);
function country_class_constructor(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
n$(geojson[x].element)
.attr("class", geojson[x].data.formats[local])
.add("svg:title");
}
}
function foo(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
n$(geojson[x].element)
.attr("class", geojson[x].data.formats[local])
.add("svg:title");
}
}
//incrementor function
function incrementor(local, geojson, dateArray) {
setTimeout(function() {
//set date label to current iteration
d3.transition(dateLabel).text(dateArray[local]);
//construct country classes
country_class_constructor(local, geojson);
// console.log(geojson);
}, 500*local);
}
///////////////////////////////
//Increment on load
///////////////////////////////
country_class_constructor(1, geojson)
for(var i=1; i< dataLength; i++) {
//Set incrementer as local variable
var local = i+1;
var timer = incrementor(local, geojson, dateArray);
}
///////////////////////////////
//interaction element
///////////////////////////////
function enable_interaction(){
var dateScale = d3.scale.linear()
.domain([1,Object.keys(dateArray).length])
.range([box.x + 10, box.x + box.width - 10])
.clamp(true);
timer = null;
overlay
.on("mouseover", mouse_over)
.on("mouseout",mouse_out)
.on("mousemove",mouse_move)
.on("touchmove",mouse_move);
function mouse_over() {
dateLabel.classed("active", true);
}
function mouse_out() {
dateLabel.classed("active", false);
}
function mouse_move() {
update_map(dateScale.invert(d3.mouse(this)[0]),data);
// displayYear(dateScale.invert(d3.mouse(this)[0]));
}
function update_map(userInput) {
var date = Math.floor(userInput);
d3.transition(dateLabel).text(dateArray[date]);
// console.log(date);
// country_class_constructor(date, geojson);
foo(date, geojson);
}
}
}
});
</script>
Edit 2: I forgot to add the JSON format. See below for two months of data:
[
{"date":"11/90",
"contributions":{
"Algeria":7,
"Argentina":39,
"Australia":41,
"Austria":967,
"Bangladesh":5,
"Belgium":4,
"Brazil":27,
"Canada":1002,
"Chile":7,
"China":5,
"Colombia":12,
"Czech Republic":6,
"Denmark":374,
"Ecuador":21,
"Fiji":719,
"Finland":992,
"France":525,
"Germany":13,
"Ghana":892,
"Hungary":15,
"India":40,
"Indonesia":5,
"Ireland":814,
"Italy":79,
"Jordan":6,
"Kenya":7,
"Malaysia":15,
"Nepal":851,
"Netherlands":15,
"New Zealand":22,
"Nigeria":2,
"Norway":924,
"Poland":165,
"Republic of the Congo":6,
"Russia":35,
"Senegal":4,
"Serbia":17,
"Spain":63,
"Sweden":738,
"Switzerland":5,
"Turkey":2,
"United Kingdom":769,
"United States":33,
"Uruguay":10,
"Venezuela":23,
"Zambia":6
}
},
{"date":"12/90",
"contributions":{
"Algeria":7,
"Argentina":39,
"Australia":41,
"Austria":967,
"Bangladesh":5,
"Belgium":4,
"Brazil":27,
"Canada":1002,
"Chile":7,
"China":5,
"Colombia":12,
"Czech Republic":6,
"Denmark":374,
"Ecuador":21,
"Fiji":719,
"Finland":992,
"France":525,
"Germany":13,
"Ghana":892,
"Hungary":15,
"India":40,
"Indonesia":5,
"Ireland":814,
"Italy":79,
"Jordan":6,
"Kenya":7,
"Malaysia":15,
"Nepal":851,
"Netherlands":15,
"New Zealand":22,
"Nigeria":2,
"Norway":924,
"Poland":165,
"Republic of the Congo":6,
"Russia":35,
"Senegal":4,
"Serbia":17,
"Spain":63,
"Sweden":738,
"Switzerland":5,
"Turkey":2,
"United Kingdom":769,
"United States":33,
"Uruguay":10,
"Venezuela":23,
"Zambia":6
}
}
]
After hours of debugging, it turns out that the nns.js library was causing me problems. Each iteration of the animation was creating a new set of DOM objects which started to max out the browser at 25,000. The solution was to use nss to create the initial state and then using the following function to change the class of each svg element.
function country_class_constructor(local, geojson){
for(var x=0;x<geojson.length;x++){
var n = geojson[x].data.properties.name;
element = document.getElementById(n);
element.className["animVal"] = geojson[x].data.formats[local];
element.className["baseVal"] = geojson[x].data.formats[local];
}
I want to know how to show infowindow on polyline in using Google Maps Api V3? and to appear in the middle of the polyline ?!
Firstly you will need to calculate the middle/center of the polyline. This has been discussed and answered here;
https://stackoverflow.com/a/9090409/787921
Then you will have to open the infowindow;
var infowindow = new google.maps.InfoWindow({
content: "infowindow text content"});
infowindow.setPosition(midLatLng);
infowindow.open(map);
find the middle point and set your custom view .
func showPath(polyStr :String){
polyline?.map = nil
mapView1.reloadInputViews()
pathDraw = GMSPath(fromEncodedPath: polyStr)!
polyline = GMSPolyline(path: pathDraw)
polyline?.strokeWidth = 4.0
polyline?.strokeColor = UIColor.init(red: 247/255.0, green: 55/255.0, blue: 76/255.0, alpha: 1.0)
polyline?.map = mapView1
let poinsCount = pathDraw.count()
let midpoint = pathDraw.coordinate(at: poinsCount)
DispatchQueue.main.async
{
self.addMarkerPin(corrdinate: midCordinate, distance: "10 min")
}
}
func addMarkerPin(corrdinate:CLLocationCoordinate2D, distance: String)
{
let marker = GMSMarker()
marker.position = corrdinate
PathTimeView = PathInfoView.loadFromNib() //here i am load Xib file, you can use your custom view
let DynamicView=PathTimeView
DynamicView.timelbl.text = distance
UIGraphicsBeginImageContextWithOptions(DynamicView.frame.size, false, UIScreen.main.scale)
DynamicView.layer.render(in: UIGraphicsGetCurrentContext()!)
let imageConverted: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
marker.icon = imageConverted
marker.map = self.mapView1
marker.infoWindowAnchor = CGPoint(x: -1900 , y: -2000)
}
First you should got center/middle of polyline and this what works for me
private fun centerPos(points: MutableList<LatLng>): LatLng {
val middleDistance = SphericalUtil.computeLength(points).div(2)
return extrapolate(points, points.first(), middleDistance.toFloat()) ?: points[0]
}
private fun extrapolate(path: List<LatLng>, origin: LatLng, distance: Float): LatLng? {
var extrapolated: LatLng? = null
if (!PolyUtil.isLocationOnPath(
origin,
path,
false,
1.0
)
) { // If the location is not on path non geodesic, 1 meter tolerance
return null
}
var accDistance = 0f
var foundStart = false
val segment: MutableList<LatLng> = ArrayList()
for (i in 0 until path.size - 1) {
val segmentStart = path[i]
val segmentEnd = path[i + 1]
segment.clear()
segment.add(segmentStart)
segment.add(segmentEnd)
var currentDistance = 0.0
if (!foundStart) {
if (PolyUtil.isLocationOnPath(origin, segment, false, 1.0)) {
foundStart = true
currentDistance = SphericalUtil.computeDistanceBetween(origin, segmentEnd)
if (currentDistance > distance) {
val heading = SphericalUtil.computeHeading(origin, segmentEnd)
extrapolated = SphericalUtil.computeOffset(
origin,
(distance - accDistance).toDouble(),
heading
)
break
}
}
} else {
currentDistance = SphericalUtil.computeDistanceBetween(segmentStart, segmentEnd)
if (currentDistance + accDistance > distance) {
val heading = SphericalUtil.computeHeading(segmentStart, segmentEnd)
extrapolated = SphericalUtil.computeOffset(
segmentStart,
(distance - accDistance).toDouble(),
heading
)
break
}
}
accDistance += currentDistance.toFloat()
}
return extrapolated
}
then You can add infoWindow with normal way with your platform at it is differ from each platform
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.