I'm trying to draw a path between points on a map. I have an array of 2 points (for my tests).
I can draw them on the map easily but it looks like the Direction Service is not working as expected.
This is what I should get according google map:
But this is what I get:
Here is the Jsfiddle.
And here is my testing code:
var map = undefined;
function initialize()
{
var mapOptions = {
center: new google.maps.LatLng(-33.885026, 151.268316),
mapTypeId: google.maps.MapTypeId.ROADMAP,
zoom: 14
};
map = new google.maps.Map(document.getElementById("map"), mapOptions);
}
jQuery(document).ready(function($)
{
initialize();
loadPaths(map);
});
function loadPaths(gmap)
{
var latlngbounds = new google.maps.LatLngBounds(),
infoWindow = new google.maps.InfoWindow(),
pathPoints = [],
index=0,
positions = [
{latitude: "-33.88914",longitude: "151.25673"},
{latitude: "-33.888",longitude: "151.2623"},
];
// The fix
positions.reverse();
$.each(positions, function(k, v) {
var myLatlng = new google.maps.LatLng(v.latitude, v.longitude);
pathPoints.push(myLatlng);
index++;
});
// Intialize the Path Array
var path = new google.maps.MVCArray();
// Intialise the Direction Service
var service = new google.maps.DirectionsService();
var iconSymbol = {
path: 'M 40 20 L 80 20 L 100 40 L 100 140 L 20 140 L 20 40 Z',
anchor: new google.maps.Point(60, 10),
scale: 0.15,
strokeColor: '#000000',
strokeWeight: 1,
fillColor: 'steelblue',
fillOpacity: 0.8,
};
// Set the Path Stroke Color
var poly = new google.maps.Polyline({
map: gmap,
strokeColor: '#dd0000',
icons: [{
icon: iconSymbol
}]
});
// Draw the path for this vehicle
// We compute the path between each point to follow the road
for (var i = 0; i < pathPoints.length; i++) {
// If it's not the last point
if ((i + 1) < pathPoints.length) {
var src = pathPoints[i];
var des = pathPoints[i + 1];
// We had the starting point to the poly path
path.push(src);
// We compute the path between the 2 points
service.route({
origin: src,
destination: des,
travelMode: google.maps.DirectionsTravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.IMPERIAL
}, function (result, status) {
if (status == google.maps.DirectionsStatus.OK) {
// We add the new computed points
for (var i = 0, len = result.routes[0].overview_path.length; i < len; i++) {
path.push(result.routes[0].overview_path[i]);
}
}
});
}
}
// Set the path of the polyline to draw it
poly.setPath(path);
}
UPDATE
I've fixed the issue of the path thanks to #anto
But I still have an issue time to time, the path is not drew on the road. I think it's an asynchronous issue with the service callback function but I'm not sure how I can fix it.
If I relaunch the script in jsfiddle it's working randomly, but sometime I end up with this kind of drawing:
UPDATE 2
It looks like using a recursive function is fixing most of it, except that my last point is not drawn: http://jsfiddle.net/maxwell2022/wY32u/11/
As click on google map first time its create start point as click second time on map it creates end point in map and displays route on map
> var map;
> var infowindow = new google.maps.InfoWindow();
> var wayA;
> var wayB;
> var geocoder = new google.maps.Geocoder();
> var directionsDisplay = new google.maps.DirectionsRenderer({
> suppressMarkers: true,
> panel: document.getElementById('right-panel'),
> draggable: true
> });
> var directionsService = new google.maps.DirectionsService();
> var data = {};
> initMap();
> function initMap() {
> debugger;
> map = new google.maps.Map(document.getElementById('rmap'), {
> center: new google.maps.LatLng(23.030357, 72.517845),
> zoom: 15
> });
> google.maps.event.addListener(map, "click", function (event) {
> if (!wayA) {
> wayA = new google.maps.Marker({
> position: event.latLng,
> map: map,
> icon: "https://chart.googleapis.com/chart?chst=d_map_pin_letter&chld=S|00FF00|000000"
> });
> } else {
> if (!wayB) {
> debugger;
> wayB = new google.maps.Marker({
> position: event.latLng,
> map: map,
> icon: "https://chart.googleapis.com/chart?chst=d_map_pin_letter&chld=E|FF0000|000000"
> });
> calculateAndDisplayRoute(directionsService, directionsDisplay, wayA, wayB);
> }
> }
> });
> }
> function computeTotalDistance(result) {
> var total = 0;
> var myroute = result.routes[0];
> for (var i = 0; i < myroute.legs.length; i++) {
> total += myroute.legs[i].distance.value;
> }
> total = total / 1000;
> return total;
> }
> function computeTotalDuration(result) {
> var total = 0;
> var myroute = result.routes[0].legs[0].duration.text;
> return myroute;
> }
> function calculateAndDisplayRoute(directionsService, directionsDisplay, wayA, wayB) {
> debugger;
> directionsDisplay.setMap(map);
> google.maps.event.addListener(directionsDisplay, 'directions_changed', function () {
> debugger;
> calculateAndDisplayRoute(directionsService, directionsDisplay.getDirections(), wayA, wayB);
> });
> directionsService.route({
> origin: wayA.getPosition(),
> destination: wayB.getPosition(),
> optimizeWaypoints: true,
> travelMode: 'DRIVING'
> }, function (response, status) {
> if (status === 'OK') {
> debugger;
>
> var route = response.routes[0];
> wayA.setMap(null);
> wayB.setMap(null);
> pinA = new google.maps.Marker({
> position: route.legs[0].start_location,
> map: map,
> icon: "https://chart.googleapis.com/chart?chst=d_map_pin_letter&chld=S|00FF00|000000"
> }),
> pinB = new google.maps.Marker({
> position: route.legs[0].end_location,
> map: map,
> icon: "https://chart.googleapis.com/chart?chst=d_map_pin_letter&chld=E|FF0000|000000"
> });
> google.maps.event.addListener(pinA, 'click', function () {
> infowindow.setContent("<b>Route Start Address = </b>" + route.legs[0].start_address + " <br/>" + route.legs[0].start_location);
> infowindow.open(map, this);
> });
> google.maps.event.addListener(pinB, 'click', function () {
> debugger;
> infowindow.setContent("<b>Route End Address = </b>" + route.legs[0].end_address + " <br/><b>Distance=</b> " +
> computeTotalDistance(directionsDisplay.getDirections()) + " Km
> <br/><b>Travel time=</b> " +
> computeTotalDuration(directionsDisplay.getDirections()) + " <br/> " +
> route.legs[0].end_location);
> infowindow.open(map, this);
> });
> } else {
> window.alert('Directions request failed due to ' + status);
> }
> directionsDisplay.setDirections(response);
> });
> }
Related
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 am using Google Maps Distance Matrix and what I need is two different distances,
at the moment I have the following:-
var start = arrObjLatLngs[0];
var end = arrObjLatLngs[arrObjLatLngs.length - 1];
var base = new google.maps.LatLng(52.781048888889, -1.2110222222222546);
var service = new google.maps.DistanceMatrixService();
service.getDistanceMatrix(
{
origins: [base],
destinations: [start, end],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.DirectionsUnitSystem.METRIC,
avoidHighways: false,
avoidTolls: false
}, callback);
function callback(response, status) {
if (status == google.maps.DistanceMatrixStatus.OK) {
var origins = response.originAddresses;
var destinations = response.destinationAddresses;
for (var i = 0; i < origins.length; i++) {
var results = response.rows[i].elements;
for (var j = 0; j < results.length; j++) {
var element = results[j];
var distance = Math.round(parseFloat(element.distance.text));
alert(distance);
var duration = element.duration.text;
var from = origins[i];
var to = destinations[j];
}
}
}
}
When I alert($distance); I get two responses, e.g. 15 and 10
What I need is two seperate values, i.e. if I alert($runinPickup); I get 15 , if I alert alert($runinDestination); I get 10.
I thought they were in an array so I have tried alert(distance[0]) but this comes back as undefined.
Do I have to do two seperate getDistanceMatrix requests or is there a way to seperate the two values?
I need following result.
All numbers use numeric sorting and all strings use alphanumeric sort. Further more , the numeric values should be listed before the string values:
Example:
before "i","9","89","0045","b","x"
after "9","0045","89","b","i","x"
My current code looks like this: (numeric sort works but my strings are distributed to the top and bottom?! -> "b","x","9","0045","89","i")
public function compareFunction(obj1:Object, obj2:Object):int {
var id1:String = (obj1 as WdProblem).id;
var id2:String = (obj2 as WdProblem).id;
if(id1.replace(' ', '') == "n") {
var sdld:int = 0;
}
var num1:int = Number(id1);
var num2:int = Number(id2);
if(stringIsAValidNumber(id1) && stringIsAValidNumber(id2)) {
if(num1 == num2) {
return 0;
} else {
if(num1 > num2) {
return 1;
} else {
return -1;
}
}
} else if(!stringIsAValidNumber(id1) && !stringIsAValidNumber(id2)) {
return ObjectUtil.compare(id1, id2);
//return compareString(id1, id2);
} else if(!stringIsAValidNumber(id1) && stringIsAValidNumber(id2)) {
return 1;
} else if(stringIsAValidNumber(id1) && !stringIsAValidNumber(id2)) {
return -1;
}
return -1;
}
private function stringIsAValidNumber(s:String):Boolean {
return Boolean(s.match("[0-9]+(\.[0-9][0-9]?)?"));
}
My suggestion is to break it down into it's constituent parts especially if you have a scenario like this with 1 or more priority sorts. The key is to move onto the next sort only when the first sort returns they are equal.
For your case, I would build 2 sorts, one for numeric and the other for alpha-numeric, then your main sort can prioritize these by calling the sub-sort.
For example I have something similar:
private function sort2DimensionsByIncomeVsTime(a:OLAPSummaryCategory, b:OLAPSummaryCategory, fields:Array = null):int
{
var sort:OLAPSort = new OLAPSort();
var incomeSort:int = sort.sortIncome(a, b);
var nameSort:int = sort.sortName(a, b);
var yrSort:int = sort.sortYear(a, b);
var moSort:int = sort.sortMonth(a, b);
if (incomeSort == 0)
{
if (nameSort == 0)
{
if (yrSort == 0)
{
//trace(a.name, a.year, a.month, 'vs:', b.name, b.year, b.month, 'month sort:', moSort);
return moSort;
}
else return yrSort;
}
else return nameSort;
}
else return incomeSort;
}
I use following sort function for AlphaNumeric sorting:
<?xml version="1.0" encoding="utf-8"?>
<s:Application
creationComplete="onCC()"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.utils.ObjectUtil;
import mx.utils.StringUtil;
import spark.collections.Sort;
import spark.collections.SortField;
public function onCC():void
{
var acAlphaNumericString:ArrayCollection = new ArrayCollection();
acAlphaNumericString.addItem({ name: "NUM10071" });
acAlphaNumericString.addItem({ name: "NUM9999" });
acAlphaNumericString.addItem({ name: "9997" });
acAlphaNumericString.addItem({ name: "9998" });
acAlphaNumericString.addItem({ name: "9996" });
acAlphaNumericString.addItem({ name: "9996F" });
acAlphaNumericString.addItem({ name: "i" });
acAlphaNumericString.addItem({ name: "9" });
acAlphaNumericString.addItem({ name: "89" });
acAlphaNumericString.addItem({ name: "0045" });
acAlphaNumericString.addItem({ name: "b" });
acAlphaNumericString.addItem({ name: "x" });
var sf:SortField = new SortField("name");
sf.compareFunction = function(o1:Object, o2:Object):int
{
return compare(o1.name, o2.name);
}
var sort:Sort = new Sort();
sort.fields = [ sf ];
acAlphaNumericString.sort = sort;
acAlphaNumericString.refresh();
for each (var o:Object in acAlphaNumericString)
trace(o.name);
}
public static function compare(firstString:String, secondString:String):int
{
if (secondString == null || firstString == null)
return 0;
var lengthFirstStr:int = firstString.length;
var lengthSecondStr:int = secondString.length;
var index1:int = 0;
var index2:int = 0;
while (index1 < lengthFirstStr && index2 < lengthSecondStr)
{
var ch1:String = firstString.charAt(index1);
var ch2:String = secondString.charAt(index2);
var space1:String = "";
var space2:String = "";
do
{
space1 += ch1;
index1++;
if (index1 < lengthFirstStr)
ch1 = firstString.charAt(index1);
else
break;
} while (isDigit(ch1) == isDigit(space1.charAt(0)));
do
{
space2 += ch2;
index2++;
if (index2 < lengthSecondStr)
ch2 = secondString.charAt(index2);
else
break;
} while (isDigit(ch2) == isDigit(space2.charAt(0)));
var str1:String = new String(space1);
var str2:String = new String(space2);
var result:int;
if (isDigit(space1.charAt(0)) && isDigit(space2.charAt(0)))
{
var firstNumberToCompare:int = parseInt(StringUtil.trim(str1));
var secondNumberToCompare:int = parseInt(StringUtil.trim(str2));
result = ObjectUtil.numericCompare(firstNumberToCompare, secondNumberToCompare);
}
else
result = ObjectUtil.compare(str1, str2);
if (result != 0)
return result;
}
return lengthFirstStr - lengthSecondStr;
function isDigit(ch:String):Boolean
{
var code:int = ch.charCodeAt(0);
return code >= 48 && code <= 57;
}
}
]]>
</fx:Script>
</s:Application>
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.
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