Here Map Attributes v8 to v7 - Convert tileIds to tileXYs - here-api

Currently, our application uses Here Map Attributes API v7 and the response looks like this
https://pde.api.here.com/1/index.json?app_id=&app_code=&layer=ROAD_GEOM_FCn&attributes=LINK_ID&values=17339738,17339811
{
"Layers":
[
{
"layer": "ROAD_GEOM_FC3",
"level": 11,
"tileXYs":
[
{
"x": 2000,
"y": 1611
},
{
"x": 2009,
"y": 1611
}
]
}
]
}
We're trying to migrate to new V8 API and having issues with translating the response model. In v8 the response returns tileIds (instead of tileXYs)
https://smap.hereapi.com/v8/maps/index.json?layer=ROAD_GEOM_FCn&attributes=LINK_ID&values=17339738,17339811&apiKey=
{
"Layers":
[
{
"layer": "ROAD_GEOM_FC3",
"level": 11,
"tileIDs":
[
6600656,
6600665
]
}
]
}
Is there a way I can convert tileIds into tileXYs similar to the legacy response?
I tried to convert lat and long from this API endpoint like in the docs but didn't work.
https://developer.here.com/documentation/content-map-attributes/dev_guide/topics/here-map-content.html
https://smap.hereapi.com/v8/maps/attributes.json?layers=ROAD_GEOM_FC3&in=tile:6600656&apikey=

First variant:
Calculates tileX and tileY out of a given tileId and a level
#param tileId - the tile id
#param level - the PDE layer level
#param tileXYLevel - this OUT parameter is filed with tileX, tileY and the level
*/
public void getTileXYLevel(long tileId, int level, int tileXYLevel[]) throws Exception
{
if (isStaticContent) {
tileXYLevel[0] = 0;
tileXYLevel[1] = 0;
tileXYLevel[2] = 0;
return;
}
int numTilesVertical = 1L << level;
int tileX = (int)(tileId % (2 * numTilesVertical));
if (tileX < 0 || tileX >= numTilesVertical * 2)
throw new Exception("Invalid tileX=" + tileX + " for level " + level + ", must be [0 ... " + (numTilesVertical * 2 - 1) + "]");
int tileY = (int)((tileId - tileX) / (2 * numTilesVertical));
if (tileY < 0 || tileY >= numTilesVertical)
throw new Exception("Invalid tileY=" + tileY + " for level " + level + ", must be [0 ... " + (numTilesVertical - 1) + "]");
tileXYLevel[0] = tileX;
tileXYLevel[1] = tileY;
tileXYLevel[2] = level;
}
Second variant:
Regarding this formulas:
tile size = 180° / 2^level [degree]
tileY = trunc((latitude + 90°) / tile size)
tileX = trunc((longitude + 180°) / tile size)
tileID = tileY * 2 * (2^level) + tileX
This is not possible. At least it is needed to know whether latitude or longitude then you can calculate tileX or tileY.
Better way is:
1.Request this https://smap.hereapi.com/v8/maps/attributes.json?layers=ROAD_GEOM_FC3&in=tile:6600656&apikey=
In fist Row of response you have:
Meta: {
layerName: "ROAD_GEOM_FC3",
tileId: 6600656,
level: 11,
mapRegion: "WEU",
mapRelease: "22125",
},
Rows: [
{
LINK_ID: "17339811",
LONG_HAUL: "N",
NAME: "B4304",
NAMES: "ENGBNB4304",
TUNNEL: "N",
BRIDGE: "N",
LAT: "5167314,16,9,10",
LON: "-417072,-9,-6,-9",
ZLEVEL: ",,,",
ELEVATION: ",,,",
TOPOLOGY_ID: "98996232",
START_OFFSET: "0",
}
Then you have from LAT, LON:
lat: 51.67314; lng: -4.17072
2.From above coordinate you calculate tileX and tileY, see please Javascript example:
var
degSize = 180 / Math.pow(2, level),
tileY = Math.floor((lat + 90) / degSize),
tileX = Math.floor((lng + 180) / degSize);
You know from response of https://smap.hereapi.com/v8/maps/index.json? that level is 11 (or you know it from "Meta" property)
After calculation we have tileX = 2000 and tileY = 1611

Related

Google Maps Javascript API - places.AutocompleteService - getPlacePredictions returns wrong distance_meters, when certain criteria are met

Google Maps Javascript API version 3.44.5
I call getPlacePredictions to get predictions from some user input, 'origin' biased, and 'address' type. The prediction distance_meters is wrong under certain circumstances: When the input doesn't correspond to an existent street number, but is closed enough so the engine is able to estimate location. Proof of wrong distance: Take the returned place_id, use Geocoder to obtain geometry.location, finally calc distance between coords. In this example we get 547 vs 2481 meters.
Also, please conside that Villate 250 returns 547 meters (wrong), the same distance of Villate 280, and the same of Villate 350... But Villate 456 returns 2390 meters (correct)... And then again Villate 480 returns 547 meters (wrong) and so on...
Bug ? Am i misinterpreting something ? Thanks
const OriginLat = -34.52211;
const OriginLon = -58.499669;
const exampleInput = "villate 250";
var service = new google.maps.places.AutocompleteService();
var geocoder = new google.maps.Geocoder();
service.getPlacePredictions({ input: exampleInput
, type: ["address"]
, componentRestrictions: { country: 'ar' }
, origin: new google.maps.LatLng( OriginLat, OriginLon )
, radius: 1
}, displaySuggestions);
const displaySuggestions = function(predictions, status) {
// Values
console.log(predictions[0].place_id);
// EkBDYXJsb3MgVmlsbGF0ZSAyNTAsIE9saXZvcywgUHJvdmluY2lhIGRlIEJ1ZW5vcyBBaXJlcywgQXJnZW50aW5hIlESTwo0CjIJc8WRg0SxvJURdeB0wGKPz_caHgsQ7sHuoQEaFAoSCb-oQzIdsbyVEYvJh6S0OZzZDBD6ASoUChIJz6lHjCixvJURF-aUHiYnCg4
console.log(predictions[0].distance_meters);
// 547m
};
geocoder.geocode({placeId: place_id}, geocoderResult);
const geocoderResult = function(results, status) {
let resultPos = results[0].geometry.location;
console.log(resultPos);
// -34.51032,-58.476673
// Distance Recalc
distance_meters = fncLocalDistance(resultPos.lat(), resultPos.lng(), OriginLat, OriginLon);
// 2481m
};
// Distance between coords
function fncLocalDistance(lat1, lon1, lat2, lon2) {
if ((lat1 == lat2) && (lon1 == lon2)) {return 0}
let radlat1 = Math.PI * lat1/180;
let radlat2 = Math.PI * lat2/180;
let theta = lon1-lon2;
let radtheta = Math.PI * theta/180;
let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {dist = 1}
dist = Math.acos(dist);
dist = dist * 180/Math.PI;
dist = dist * 60 * 1.1515;
dist = dist * 1.609344 * 1000;
return dist;
}

d3-geo-voronoi d3-tile polygon fill problem

I am trying to use d3-geo-voronoi to display vector tile data using d3-tile. My initial attempt in displaying the data, with fill set to "none" worked, which was very exiting!
Voronoi tile map without color fill
However, when I attempted to fill the polygons, some of the tiles were distorted.
Voronoi tile map with color fill
I've not been able to figure out why this is happening. I checked the svg's in the dom, and everything looks correct. The svg's are correct where there are no polygons, they are just not being rendered properly, possibly they are being covered up. Below is the code I used:
const d3 = require('d3');
const d3tile = require('d3-tile');
const d3geovoronoi = require('d3-geo-voronoi');
const vt2geojson = require('#mapbox/vt2geojson');
const pi = Math.PI,
tau = 2 * pi;
const width = Math.max(960, window.innerWidth),
height = Math.max(500, window.innerHeight);
const map = d3.select("body").append("div")
.attr("class", "map")
.style("width", width + "px")
.style("height", height + "px")
.on("mousemove", mousemoved);
let projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
let center = projection([-76.3, 38.794745]);
const tile = d3tile.tile()
.size([width, height]);
const zoom = d3.zoom()
.scaleExtent([1 << 15, 1 << 24])
.on("zoom", zoomed);
const svg = map.append("g")
.attr("pointer-events", "none")
.attr("class", "svg");
const info = map.append("g")
.attr("class", "info");
const ramp = d3.scaleLinear().domain([0.05,0.07]).interpolate(d3.interpolateHcl).range(['#34d8eb','#3a34eb']).unknown("#5c5752")
map.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 21)
.translate(-center[0], -center[1]));
function zoomed() {
let transform = d3.event.transform;
let tiles = tile(transform);
let image = svg
.style("transform", stringify(tiles.scale, tiles.translate))
.selectAll(".tile")
.data(tiles, function(d) { return d; })
.enter().append("svg")
.attr("class", "tile")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", "0.5")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.style("left", function(d) { return d[0] * 256 + "px"; })
.style("top", function(d) { return d[1] * 256 + "px"; })
.each(function(d) { this._xhr = render(d, this); });
projection
.scale(transform.k / tau)
.translate([transform.x, transform.y]);
}
function render(d, xnode) {
let k = Math.pow(2, d[2]) * 256;
vt2geojson({
uri: 'http://localhost:7800/public.r3sim_fort_temp/'+d[2]+'/'+d[0]+'/'+d[1]+'.pbf?properties=node,zeta,mask,bathymetry'
}, function (err, json) {
if (err) throw err;
d3.select(xnode)
.selectAll("path")
.data(d3geovoronoi.geoVoronoi().polygons(json).features)
.enter().append("path")
//.attr('fill', 'none')
.attr("fill", function(d) {return ramp(d.properties.site.properties.zeta)})
.attr("stroke", "#fff")
.attr("stroke-width", "0.5")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", d3.geoPath()
.projection(d3.geoMercator()
.scale(k / tau)
.translate([k / 2 - d[0] * 256, k / 2 - d[1] * 256])
.precision(0)));
})
}
function stringify(scale, translate) {
const k = scale / 256, r = scale % 1 ? Number : Math.round;
return "matrix3d(" + [k, 0, 0, 0, 0, k, 0, 0, 0, 0, k, 0, r(translate[0] * scale), r(translate[1] * scale), 0, 1 ] + ")";
}
function mousemoved() {
info.text(formatLocation(projection.invert(d3.mouse(this)), d3.zoomTransform(this).k));
}
function formatLocation(p, k) {
const format = d3.format("." + Math.floor(Math.log(k) / 2 - 2) + "f");
return (p[1] < 0 ? format(-p[1]) + "°S" : format(p[1]) + "°N") + " "
+ (p[0] < 0 ? format(-p[0]) + "°W" : format(p[0]) + "°E");
}
<!doctype html>
<head>
<meta charset="utf-8">
<title>D3 V5 Vector Tile Example</title>
<style>
body {
margin: 0;
}
.map {
background: #5c5752;
position: relative;
overflow: hidden;
}
.svg {
position: absolute;
will-change: transform;
}
.tile {
position: absolute;
width: 256px;
height: 256px;
}
.info {
position: absolute;
bottom: 10px;
left: 10px;
}
</style>
</head>
<body>
<script src="bundle.js"></script>
</body>
In this example I filled the polygons with varying color values. However, the exact same distortions occur if I use a single color value. The distortions are also always in the same place, if I reload all of the data.
I did a deeper dive into the data, and found the bad svg path, and then found the data related to it. It looks like d3.geo.voronoi is producing some bad coordinates, but the input data looks okay. Below are two printouts of node 1192. The first is the input geojson data, showing the coordinates, and the second is the voronoi geometry. The voronoi geometry contains longitude values in the eastern hemisphere (103.86...), which is way outside of the range of the data. I'm still trying to determine why these bad values are being produced. Again, the input coordinates look correct, but possibly it is other data that goes into the voronoi calculation?
1192
{…}
geometry: {…}
coordinates: (2) […]
0: -76.12801194190979
1: 38.78622954627738
length: 2
<prototype>: Array []
type: "Point"
<prototype>: Object { … }
properties: Object { node: 180407, zeta: "NaN", mask: "True", … }
type: "Feature"
<prototype>: Object { … }
1192 (11) […]
0: Array [ 103.86695733932268, -44.964779133003304 ]
1: Array [ -76.13308210176842, 38.75793814039401 ]
2: Array [ -76.13020999558496, 38.782688154120585 ]
3: Array [ -76.12890669699081, 38.78647064351637 ]
4: Array [ -76.12807302385534, 38.786723650244355 ]
5: Array [ -76.12754554182737, 38.78651000385868 ]
6: Array [ -76.12640847594942, 38.78408839960177 ]
7: Array [ -76.11435851540921, 38.636536130021334 ]
8: Array [ 103.858096036925, -39.00570100251519 ]
9: Array [ 103.860092112702, -39.367933188411186 ]
10: Array [ 103.86695733932268, -44.964779133003304 ]
length: 11
<prototype>: []

Detect if Lines Intersect in Google Charts or Plot.ly

I have seen scripts that claim to enter coordinates and it'll tell you if they intersect, but I have an array of X,Y values for a couple of "lines" but how do I cycle through the points to find out if they intersect?
I've included a photo of my graph and as you see, eventually my plots cross over, I just want to know if my values ever cross over (intersect).
How do I run through this to find out if any intersection ever occurs?
var Test = {
x: [8043, 10695, 13292, 17163, 20716, 25270],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test'
};
var Test2 = {
x: [8043, 10063, 12491, 16081, 19408, 23763],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test2'
};
var Test3 = {
x: [4700, 5943, 7143, 8841, 10366, 13452],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test3'
};
var data = [Test, Test2, Test3];
var layout = {
width: 700,
height: 700,
xaxis: {
type: 'log',
range: [3,5]
},
yaxis: {
type: 'log',
range: [-2,3]
}
};
Plotly.newPlot('myDiv', data,layout);
Path intercepts
This answer is a follow on from my answer to your most resent question.
The code snippet below will find the intercepts of the paths in the tables as structured in this questions example data using a modified intercept function from the answer link in may comment from aforementioned answer.
Note I am assuming that each table eg Test in your example data represents a curve (Path as a set of line segments) and that intercepts are not expected within a table but rather between tables.
Basic solution
It does this by checking each line segment in one table against each line segment in the other and storing all intercepts in an array.
Note that if a intercept is found at the start or end point of a line it may appear in the array of intercepts twice as the intercept test includes these points.
Note lines that are parallel, even if they have matching start and or end points will not count as intercepts.
The example is run against the example data and has a verbose console output to guide, if needed, you working through what ever data sets you are wrangling. The console logs can be removed without ill effect.
var Test = {
x: [8043, 10695, 13292, 17163, 20716, 25270],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test'
};
var Test2 = {
x: [8043, 10063, 12491, 16081, 19408, 23763],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test2'
};
var Test3 = {
x: [4700, 5943, 7143, 8841, 10366, 13452],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test3'
};
// Copy from here to end comment and place into you page (code base)
// lines outputting to the console eg console.log are just there to help you out
// and can be removed
const lineIntercepts = (() => {
const Point = (x, y) => ({x, y});
const Line = (p1, p2) => ({p1, p2});
const Vector = line => Point(line.p2.x - line.p1.x, line.p2.y - line.p1.y);
function interceptSegs(line1, line2) {
const a = Vector(line1), b = Vector(line2);
const c = a.x * b.y - a.y * b.x;
if (c) {
const e = Point(line1.p1.x - line2.p1.x, line1.p1.y - line2.p1.y);
const u = (a.x * e.y - a.y * e.x) / c;
if (u >= 0 && u <= 1) {
const u = (b.x * e.y - b.y * e.x) / c;
if (u >= 0 && u <= 1) {
return Point(line1.p1.x + a.x * u, line1.p1.y + a.y * u);
}
}
}
}
const PointFromTable = (t, idx) => Point(t.x[idx], t.y[idx]);
const LineFromTable = (t, idx) => Line(PointFromTable(t, idx++), PointFromTable(t, idx));
return function (table1, table2) {
const results = [];
var i = 0, j;
while (i < table1.x.length - 1) {
const line1 = LineFromTable(table1, i);
j = 0;
while (j < table2.x.length - 1) {
const line2 = LineFromTable(table2, j);
const point = interceptSegs(line1, line2);
if (point) {
results.push({
description: `'${table1.name}' line seg index ${i}-${i+1} intercepts '${table2.name}' line seg index ${j} - ${j+1}`,
// The description (line above) can be replaced
// with relevant data as follows
/* remove this line to include additional info per intercept
tableName1: table1.name,
tableName2: table2.name,
table_1_PointStartIdx: i,
table_1_PointEndIdx: i + 1,
table_2_PointStartIdx: j,
table_2_PointEndIdx: j + 1,
and remove this line */
x: point.x,
y: point.y,
});
}
j ++;
}
i++;
}
if (results.length) {
console.log("Found " + results.length + " intercepts for '" + table1.name + "' and '" + table2.name + "'");
console.log(results);
return results;
}
console.log("No intercepts found for '" + table1.name + "' and '" + table2.name + "'");
}
})();
// end of code
// Test and example code only from here down.
var res1 = lineIntercepts(Test, Test2);
var res2 = lineIntercepts(Test, Test3);
var res3 = lineIntercepts(Test2, Test3);
Using the above function
This bit of code illustrates how you extract intercepts from the function results
// find all the intercepts for the paths in tabels Test and Test2
const results = lineIntercepts(Test, Test2); // pass two tables
// If results not undefined then intercepts have been found
if (results) { // results is an array of found intercepts
// to get the point/s as there could be several
for (const intercept of results) { // loop over every intercept
// a single intercept coordinate
const x = intercept.x; // get x
const y = intercept.y; // get y
}
}
Better solutions
The paths look very much like they are a plot of some function thus there are even simpler solutions.
Rather than list out lines of code, I will direct you towards graphing calculators in case you are unaware of such useful time savers. They would have solved your problem in the time it takes to enter the data (by copy&paste thats not very long)
Online graphing calculators example apps Geogebra and Desmos and many more.

Calculate the distance between 2 locations and calculate a price

So basically I have this script (credits to 1stwebdesigner.com and geocodezip) which calculates the distance between 4 locations.
Location 1, location 2, location 3, location 4.
I now need to calculate the distance between location2 and location 3.
and also get that distance and display it and display a price according to this formula
0-10kms = $99,
11-20kms plus $5 per each additional km,
21 - 35kms plus $3.75 per each additional km,
36kms+ plus $3.50 per each additional km,
How do I go about this?
So far I have this (Credits to Rajaprabhu)
(Price Calculation based on the distance covered)
, problem 1 is, the formula is wrong for my situation and problem 2 is the formula isn't getting the distance from the previous calculation.
var request = {
origin:location2,
destination:location3,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status)
{
if (status == google.maps.DirectionsStatus.OK)
{
var distance = response.routes[0].legs[0].distance.text;
var duration = response.routes[0].legs[0].duration.text;
var dvDistance = document.getElementById("dvDistance");
dvDistance.innerHTML = "";
dvDistance.innerHTML += "The one way Distance is: " + distance + "<br />";
dvDistance.innerHTML += "The one way Duration is: " + duration;
//calculate the one way price using the klms
var kms = distance;
var price_1 = (kms > 0) ? 3 : 0; kms = (kms > 0)? kms - 1 : 0;
var price_2 = (kms - 14) > 0 ? (14 * 1.60) : (kms * 1.60); kms = (kms-14)>0 ? kms - 14 : 0;
var price_3 = (kms - 15) > 0 ? (15 * 1.40) : (kms * 1.40); kms = (kms-15)>0 ? kms - 15 : 0;
var price_4 = (kms > 0) ? (kms * 1.20) : 0;
document.getElementById("displayprice").innerHTML = "the one way price is: " + (price_1 + price_2 + price_3 + price_4);
}
});
Stuck on the formula now.
1. anything over 35km gives a incorrect result.
2. 10km and under should really default to $99.
//calculate the one way price using the klms
var kms = distance;
console.log(kms);
var price_1 = (kms > 0) ? 99 : 0; kms = (kms > 0)? kms - 10 : 0;
var price_2 = (kms - 10) > 0 ? (10 * 5.00) : (kms * 5.00); kms = (kms-10)>0 ? kms - 10 : 0;
var price_3 = (kms - 20) > 0 ? (15 * 3.75) : (kms * 3.75); kms = (kms-20)>0 ? kms - 20 : 0;
var price_4 = (kms > 0) ? (kms * 3.50) : 0;
From the documentation:
google.maps.Distance object specification
A representation of distance as a numeric value and a display string.
Properties
text | Type: string | A string representation of the distance value, using the UnitSystem specified in the request.
value | Type: number | The distance in meters.
Your distance is a string: var distance = response.routes[0].legs[0].distance.text;
You should use the numeric value (in meters):
var distance = response.routes[0].legs[0].distance.value/1000;
proof of concept fiddle
code snippet:
var geocoder;
var map;
// New York, NY, USA (40.7127837, -74.00594130000002)
// Newark, NJ, USA (40.735657, -74.1723667)
// Philadelphia, PA, USA (39.9525839, -75.16522150000003)
// Baltimore, MD, USA (39.2903848, -76.61218930000001)
var location1 = new google.maps.LatLng(40.7127837, -74.005941);
var location2 = new google.maps.LatLng(40.735657, -74.1723667);
var location3 = new google.maps.LatLng(39.9525839, -75.1652215);
var location4 = new google.maps.LatLng(39.2903848, -76.6121893);
function initialize() {
// create a new map object
// set the div id where it will be shown
// set the map options
var mapOptions = {
center: {
lat: 42,
lng: -72
},
zoom: 4
}
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
// show route between the points
directionsService = new google.maps.DirectionsService();
directionsDisplay = new google.maps.DirectionsRenderer({
suppressMarkers: true,
suppressInfoWindows: true
});
directionsDisplay.setMap(map);
var request = {
origin: location1,
waypoints: [{
location: location2,
}, {
location: location3,
}],
destination: location4,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
var route = response.routes[0];
var summaryPanel = document.getElementById("directions_panel");
summaryPanel.innerHTML = "";
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
var routeSegment = i + 1;
summaryPanel.innerHTML += "<b>Route Segment: " + routeSegment + "</b><br />";
summaryPanel.innerHTML += route.legs[i].start_address + " to ";
summaryPanel.innerHTML += route.legs[i].end_address + "<br />";
summaryPanel.innerHTML += route.legs[i].distance.text + "<br />";
summaryPanel.innerHTML += route.legs[i].duration.text + "<br />";
}
computeTotalDistance(response);
var request = {
origin: location2,
destination: location3,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distance = response.routes[0].legs[0].distance.value / 1000;
var duration = response.routes[0].legs[0].duration.text;
var dvDistance = document.getElementById("dvDistance");
dvDistance.innerHTML = "";
dvDistance.innerHTML += "The one way Distance (segment 2) is: " + distance + "<br />";
dvDistance.innerHTML += "The one way Duration (segment 2) is: " + duration;
//calculate the one way price using the klms
var kms = distance;
console.log(kms);
var price_1 = (kms > 0) ? 3 : 0;
kms = (kms > 0) ? kms - 1 : 0;
var price_2 = (kms - 14) > 0 ? (14 * 1.60) : (kms * 1.60);
kms = (kms - 14) > 0 ? kms - 14 : 0;
var price_3 = (kms - 15) > 0 ? (15 * 1.40) : (kms * 1.40);
kms = (kms - 15) > 0 ? kms - 15 : 0;
var price_4 = (kms > 0) ? (kms * 1.20) : 0;
document.getElementById("displayprice").innerHTML = "the one way price (segment 2) is: $" + (price_1 + price_2 + price_3 + price_4).toFixed(2);
}
});
} else {
alert("directions response " + status);
}
});
}
google.maps.event.addDomListener(window, "load", initialize);
function computeTotalDistance(result) {
var totalDist = 0;
var totalTime = 0;
var myroute = result.routes[0];
for (i = 0; i < myroute.legs.length; i++) {
totalDist += myroute.legs[i].distance.value;
totalTime += myroute.legs[i].duration.value;
}
totalDist = totalDist / 1000.
document.getElementById("total").innerHTML = "total distance is: " + totalDist + " km<br>total time is: " + (totalTime / 60).toFixed(2) + " minutes<br>total price is: $" + ((totalTime / 60).toFixed(2) * 2.1) + " dollars<br>saturday price is: $" + ((totalTime / 60).toFixed(2) * 2.35) + " dollars<br>sunday price is: $" + ((totalTime / 60).toFixed(2) * 2.6) + " dollars";
}
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<div id="map_canvas"></div>
<div id="dvDistance"></div>
<div id="displayprice"></div>
<div id="total"></div>
<div id="directions_panel"></div>

Deconstructing Google maps smarty pins animation

Updates
Updated fiddle to simplify what is going on:
added four buttons to move the stick, each button increments the value by 30 in the direction
plotted x and y axis
red line is the stick, with bottom end coordinates at (ax,ay) and top end coordinates at (bx,by)
green line is (presumably) previous position of the stick, with bottom end coordinates at (ax, ay) and top end coordinates at (bx0, by0)
So, after having my ninja moments. I'm still nowhere near understanding the sorcery behind unknownFunctionA and unknownFunctionB
For the sake of everyone (all two of you) here is what I've sort of learnt so far
function unknownFunctionB(e) {
var t = e.b.x - e.a.x
, n = e.b.y - e.a.y
, a = t * t + n * n;
if (a > 0) {
if (a == e.lengthSq)
return;
var o = Math.sqrt(a)
, i = (o - e.length) / o
, s = .5;
e.b.x -= t * i * .5 * s,
e.b.y -= n * i * .5 * s
}
}
In the unknownFunctionB above, variable o is length of the red sitck.
Still don't understand
What is variable i and how is (bx,by) calculated? essentially:
bx = bx - (bx - ax) * 0.5 * 0.5
by = by - (by - ay) * 0.5 * 0.5
In unknownFunctionA what are those magic numbers 1.825 and 0.825?
Below is irrelevant
I'm trying to deconstruct marker drag animation used on smartypins
I've managed to get the relevant code for marker move animation but I'm struggling to learn how it all works, especially 2 functions (that I've named unknownFunctionA and unknownFunctionB)
Heres the StickModel class used on smartypins website, unminified to best of my knowledge
function unknownFunctionA(e) {
var t = 1.825
, n = .825
, a = t * e.x - n * e.x0
, o = t * e.y - n * e.y0 - 5;
e.x0 = e.x,
e.y0 = e.y,
e.x = a,
e.y = o;
}
function unknownFunctionB(e) {
var t = e.b.x - e.a.x
, n = e.b.y - e.a.y
, a = t * t + n * n;
if (a > 0) {
if (a == e.lengthSq)
return;
var o = Math.sqrt(a)
, i = (o - e.length) / o
, s = .5;
e.b.x -= t * i * .5 * s,
e.b.y -= n * i * .5 * s
}
}
function StickModel() {
this._props = function(e) {
return {
length: e,
lengthSq: e * e,
a: {
x: 0,
y: 0
},
b: {
x: 0,
y: 0 - e,
x0: 0,
y0: 0 - e
},
angle: 0
}
}
(60)
}
var radianToDegrees = 180 / Math.PI;
StickModel.prototype = {
pos: {
x: 0,
y: 0
},
angle: function() {
return this._props.angle
},
reset: function(e, t) {
var n = e - this._props.a.x
, a = t - this._props.a.y;
this._props.a.x += n,
this._props.a.y += a,
this._props.b.x += n,
this._props.b.y += a,
this._props.b.x0 += n,
this._props.b.y0 += a
},
move: function(e, t) {
this._props.a.x = e,
this._props.a.y = t
},
update: function() {
unknownFunctionA(this._props.b),
unknownFunctionB(this._props),
this.pos.x = this._props.a.x,
this.pos.y = this._props.a.y;
var e = this._props.b.x - this._props.a.x
, t = this._props.b.y - this._props.a.y
, o = Math.atan2(t, e);
this._props.angle = o * radianToDegrees;
}
}
StickModel.prototype.constructor = StickModel;
Fiddle link with sample implementation on canvas: http://jsfiddle.net/vff1w82w/3/
Again, Everything works as expected, I'm just really curious to learn the following:
What could be the ideal names for unknownFunctionA and unknownFunctionB and an explanation of their functionality
What are those magic numbers in unknownFunctionA (1.825 and .825) and .5 in unknownFunctionB.
Variable o in unknownFunctionB appears to be hypotenuse. If that's the case, then what exactly is i = (o - e.length) / o in other words, i = (hypotenuse - stickLength) / hypotenuse?
First thing I'd recommend is renaming all those variables and methods until they start making sense. I also removed unused code.
oscillator
adds wobble to the Stick model by creating new position values for the Stick that follows the mouse
Exaggerates its movement by multiplying its new position by 1.825 and also subtracting the position of an "echo" of its previous position multiplied by 0.825. Sort of looking for a middle point between them. Helium makes the stick sit upright.
overshooter minus undershooter must equal 1 or you will have orientation problems with your stick. overshooter values above 2.1 tend to make it never settle.
seekerUpdate
updates the seeker according to mouse positions.
The distance_to_cover variable measures the length of the total movement. You were right: hypothenuse (variable o).
The ratio variable calculates the ratio of the distance that can be covered subtracting the size of the stick. The ratio is then used to limit the adjustment of the update on the seeker in both directions (x and y). That's how much of the update should be applied to prevent overshooting the target.
easing slows down the correct updates.
There are lots of interesting info related to vectors on the book The nature of code.
function oscillator(seeker) {
var overshooter = 1.825;
var undershooter = .825;
var helium = -5;
var new_seeker_x = overshooter * seeker.x - undershooter * seeker.echo_x;
var new_seeker_y = overshooter * seeker.y - undershooter * seeker.echo_y + helium;
seeker.echo_x = seeker.x;
seeker.echo_y = seeker.y;
seeker.x = new_seeker_x;
seeker.y = new_seeker_y;
}
function seekerUpdate(stick) {
var dX = stick.seeker.x - stick.mouse_pos.x;
var dY = stick.seeker.y - stick.mouse_pos.y;
var distance_to_cover = Math.sqrt(dX * dX + dY * dY);
var ratio = (distance_to_cover - stick.length) / distance_to_cover;
var easing = .25;
stick.seeker.x -= dX * ratio * easing;
stick.seeker.y -= dY * ratio * easing;
}
function StickModel() {
this._props = function(length) {
return {
length: length,
lengthSq: length * length,
mouse_pos: {
x: 0,
y: 0
},
seeker: {
x: 0,
y: 0 - length,
echo_x: 0,
echo_y: 0 - length
}
}
}(60)
}
StickModel.prototype = {
move: function(x, y) {
this._props.mouse_pos.x = x;
this._props.mouse_pos.y = y;
},
update: function() {
oscillator(this._props.seeker);
seekerUpdate(this._props);
}
};
StickModel.prototype.constructor = StickModel;
// Canvas to draw stick model coordinates
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
canvas.width = window.outerWidth;
canvas.height = window.outerHeight;
var canvasCenterX = Math.floor(canvas.width / 2);
var canvasCenterY = Math.floor(canvas.height / 2);
context.translate(canvasCenterX, canvasCenterY);
var stickModel = new StickModel();
draw();
setInterval(function() {
stickModel.update();
draw();
}, 16);
$(window).mousemove(function(e) {
var mouseX = (e.pageX - canvasCenterX);
var mouseY = (e.pageY - canvasCenterY);
stickModel.move(mouseX, mouseY);
stickModel.update();
draw();
});
function draw() {
context.clearRect(-canvas.width, -canvas.height, canvas.width * 2, canvas.height * 2);
// red line from (ax, ay) to (bx, by)
context.beginPath();
context.strokeStyle = "#ff0000";
context.moveTo(stickModel._props.mouse_pos.x, stickModel._props.mouse_pos.y);
context.lineTo(stickModel._props.seeker.x, stickModel._props.seeker.y);
context.fillText('mouse_pos x:' + stickModel._props.mouse_pos.x + ' y: ' + stickModel._props.mouse_pos.y, stickModel._props.mouse_pos.x, stickModel._props.mouse_pos.y);
context.fillText('seeker x:' + stickModel._props.seeker.x + ' y: ' + stickModel._props.seeker.y, stickModel._props.seeker.x - 30, stickModel._props.seeker.y);
context.lineWidth = 1;
context.stroke();
context.closePath();
// green line from (ax, ay) to (bx0, by0)
context.beginPath();
context.strokeStyle = "#00ff00";
context.moveTo(stickModel._props.mouse_pos.x, stickModel._props.mouse_pos.y);
context.lineTo(stickModel._props.seeker.echo_x, stickModel._props.seeker.echo_y);
context.fillText('echo x:' + stickModel._props.seeker.echo_x + ' y: ' + stickModel._props.seeker.echo_y, stickModel._props.seeker.echo_x, stickModel._props.seeker.echo_y - 20);
context.lineWidth = 1;
context.stroke();
context.closePath();
// blue line from (bx0, by0) to (bx, by)
context.beginPath();
context.strokeStyle = "#0000ff";
context.moveTo(stickModel._props.seeker.echo_x, stickModel._props.seeker.echo_y);
context.lineTo(stickModel._props.seeker.x, stickModel._props.seeker.y);
context.stroke();
context.closePath();
}
body {
margin: 0px;
padding: 0px;
}
canvas {
display: block;
}
p {
position: absolute;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<p>Move your mouse to see the stick (colored red) follow</p>
<canvas id="myCanvas"></canvas>

Resources