Openlayers 4 "haversineDistance" calculate differently (compared to ol.Sphere.getLength() ) distance between 2 points.
Why?
Standard example with my custom code
var formatLength = function(line) {
var length = ol.Sphere.getLength(line);
console.log('');
console.log('');
console.log('--1: ', length);
var wgs84Sphere = new ol.Sphere(6378137);
var arr = line.getCoordinates();
var lenghtC = wgs84Sphere.haversineDistance(arr[0], arr[1]) / 100000;
console.log('--2: ', lenghtC);
var output;
if (length > 100) {
output = (Math.round(length / 1000 * 100) / 100) +
' ' + 'km';
} else {
output = (Math.round(length * 100) / 100) +
' ' + 'm';
}
return output;
};
Reading the OpenLayers 4 source code is quite helpful in this case.
The getLength() and haversineDistance() will eventually use the same algorithm to compute a distance:
ol.Sphere.getDistance_ = function(c1, c2, radius) {
var lat1 = ol.math.toRadians(c1[1]);
var lat2 = ol.math.toRadians(c2[1]);
var deltaLatBy2 = (lat2 - lat1) / 2;
var deltaLonBy2 = ol.math.toRadians(c2[0] - c1[0]) / 2;
var a = Math.sin(deltaLatBy2) * Math.sin(deltaLatBy2) + Math.sin(deltaLonBy2) * Math.sin(deltaLonBy2) * Math.cos(lat1) * Math.cos(lat2);
return 2 * radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};
The main difference is that haversineDistance() method only applies to two coordinates, whereas getLength() is more powerful and will sum the distance for any number of coordinates, managing different kind of geometries.
Therefore, for a simple line distance between two points, you should not see any difference.
If there's one, it may be because you are using a different geometry type, or because you are not working in the same projection. The getLength() method works by default with EPSG:3857 / EPSG:4326.
ol.Sphere.getLength = function(geometry, opt_options) {
var options = opt_options || {};
var radius = options.radius || ol.Sphere.DEFAULT_RADIUS;
var projection = options.projection || 'EPSG:3857';
geometry = geometry.clone().transform(projection, 'EPSG:4326');
var type = geometry.getType();
[...]
Note that these calls are likely to change in OpenLayers 5 as explained here
Updated sample code
The problem is in the projection.
var wgs84Sphere = new ol.Sphere(6371008.8);
var length = wgs84Sphere.haversineDistance(cor1, cor2); // Must be 'EPSG:4326'
Openlayer "works" (mostly) with 'EPSG:3857' (Mercator).
But "ol.Sphere.haversineDistance" need 'EPSG:4326' (Lat/Long).
It has not been written in the documentation.
ol.Sphere.getLength() geometry by default is 'EPSG:3857'. The following private functions are converted to 'EPSG:4326'.
Related
I am populating 3 arrays and I've no idea which one is being populated (depends on user). My goal is 2 fold.
find which array is being populated
find the Max value Among the 3 arrays (or the arrays which are populated)
eg:
hrGenY = [100,101,102]
cadGenY = [80, 81, 82]
powerGenY = [100,104,106]
This is what I'm doing right now. (This code here is just to determine which array is being populated and NOT nil)
var whichTelemery: [Int] = []
if !powerGenY.isEmpty {
whichTelemery = powerGenY
} else if !hrGenY.isEmpty {
whichTelemery = hrGenY
} else if !cadGenY.isEmpty {
whichTelemery = cadGenY
}
after which I utilise this to determine the X & Y scale for my chart. I use whichever is the Max to normalise the scale.
var yMax: CGFloat = 0.0
var xMax: CGFloat = 0.0
var yMaxTemp: [CGFloat] = [0,0]
var xMaxTemp: [CGFloat] = [0,0]
// Determine yMax & xMax for Scale Resolution
if !dataPointsY.isEmpty {
xMaxTemp[0] = dataPointsX.max()!
yMaxTemp[0] = CGFloat((dataPointsY.max()! >= ftpPlus) ? (1.2*dataPointsY.max()!) : ftpPlus)
xMax = xMaxTemp.max()!
yMax = yMaxTemp.max()!
}
if !whichTelemery.isEmpty && dataPointsY.isEmpty {
xMaxTemp[1] = CGFloat(whichTelemery.count < 900 ? 900 : whichTelemery.count + 1)
yMaxTemp[1] = CGFloat((CGFloat(whichTelemery.max()!) >= ftpPlus) ? (1.2*CGFloat(whichTelemery.max()!)) : ftpPlus)
xMax = xMaxTemp.max()!
yMax = yMaxTemp.max()!
}
The problem with this method is I am making the assumption that the user's power (if populated) will always be higher than the HR(heart rate) which may not be true. When this happens, the HR line will not get drawn as it's above the yMax limit.
The way I'm thinking of doing it is very crude which would involve yet another group of checks to see which of the 3 arrays has the highest number. Would appreciate some better more elegant way.
Thanks
What about getting the max value for your normalisation by doing
let max = [hrGenY.max() ?? 0.0, cadGenY.max() ?? 0.0, powerGenY.max() ?? 0.0].max()
I am in a loop, reading 2 columns from a file. I read R, T combinations, 50 times. I want R and T to be in an array so I can look up the Nth pair of R, T later in a function. How do I put the R, T pairs in an array and look up the, say, 25th entry later in a function?
For example:
for (nsection in 1 until NS+1) {
val list: List<String> = lines[nsection + 1].trim().split("\\s+".toRegex())
val radius = list[0].toFloat()
println("Radius = $radius")
val twist = list[8].toFloat()
println("twist = $twist")
}
Would like to pull radius and twist pairs from a table in a function later. NS goes up to 50 so far.
You can use map() on your range iterator to produce a List of what you want.
val radiusTwistPairs: List<Pair<Float, Float>> = (1..NS).map { nsection ->
val list = lines[nsection + 1].trim().split("\\s+".toRegex())
val radius = list[0].toFloat()
println("Radius = $radius")
val twist = list[8].toFloat()
println("twist = $twist")
radius to twist
}
Or use an Array constructor:
val radiusTwistPairs: Array<Pair<Float, Float>> = Array(NS) { i ->
val list = lines[i + 2].trim().split("\\s+".toRegex())
val radius = list[0].toFloat()
println("Radius = $radius")
val twist = list[8].toFloat()
println("twist = $twist")
radius to twist
}
I have set of json data which have store name ,store_lattitude and store_longitude.I want to sort the store in the order closer to user current location.
Below is the sample JSON data:
[{"id":"1","store_name":"Nilgiris","store_address":"QWE","store_latitude":"12.929328","store_longitude":"77.605117"},
{"id":"2","store_name":"WallMart","store_address":"ABC","store_latitude":"12.979024","store_longitude":"77.611141"},
{"id":"3","store_name":"Green Mart","store_address":"XYZ","store_latitude":"12.961398","store_longitude":"77.553128"}]
Any help is appreciated. Thnaks.
You can loop through the latlongs for the user and location off to a function that calculates distance based on the Haversine formula:
In your controller, after loading your data:
angular.forEach(results, function(s) {
store['dist'] = getDistanceInM(user_lat, user_lon, store['store_latitude'], store['store_longitude']);
})
These functions can be either in your controller or in a service:
var getDistanceInM = function(lat1, lon1, lat2, lon2) {
var R = 6371;
var dLat = deg2rad(lat2 - lat1);
var dLon = deg2rad(lon2 - lon1);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2)
* Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c * 1000;
return parseInt(d, 10);
};
var deg2rad = function(deg) {
return deg * (Math.PI / 180);
};
In your view:
<ion-item ng-repeat="item in data.stores | orderBy:'dist'">
You could also leverage what's already out there with google api:
https://developers.google.com/maps/documentation/distance-matrix/start
The added benefit is that you can get various measures of distance and can even bring travel time into the mix. Also it resolves the location from text. If you can google search the location you don't need to provide long or lat. 2500 reqs daily are free.
I have an array of values [CGFloat] and array of days [CGFloat] with which each value is associated (the time is also important so the days array is a decimal value).
Each day has between 0 and n values. (n typically less than 5 or 6)
I want to find the mean value for each day, so after the calculations I would like to have an array of means [CGFloat] and an array of days [CGFloat], or a dictionary of the two combined or an array of [CGPoint]. I am fairly certain this can be done with either a mapping or reducing or filter function, but I'm having trouble doing so.
For instance
the third day might look like [2.45, 2.75, 2.9]
with associated values [145.0, 150.0, 160.0]
And I would like to end with day[2] = 2.7
and value[2] = 151.7
or
[CGPoint(2.7, 151.7)] or [2.7 : 151.7]
Can anyone offer guidance?
Some code:
let xValues : [CGFloat] = dates.map{round((CGFloat($0.timeIntervalSinceDate(dateZero))/(60*60*24))*100)/100}
let yValues : [CGFloat] = valueDoubles.map{CGFloat($0)}
//xValues and yValues are the same length
var dailyMeans = [CGFloat]()
var xVals = [CGFloat]()
let index = Int(ceil(xValues.last!))
for i in 0..<index{
let thisDay = xValues.enumerate().filter{$0.element >= CGFloat(i) && $0.element < CGFloat(i+1)}
if thisDay.count > 0{
var sum : CGFloat = 0
var day : CGFloat = 0
for i in thisDay{
sum += yValues[i.index]
day += xValues[i.index]
}
dailyMeans.append(sum/CGFloat(thisDay.count))
xVals.append(day/CGFloat(thisDay.count))
}
}
The above code works, but also has to do that enumerate.filter function values.count * days.last times. So for 40 days and 160 readings.. like 6500 times. And I'm already using way too much processing power as it is. Is there a better way to do this?
edit: forgot a line of code defining index as the ceiling of xValues.last
This has been seen 1000 times so I thought I would update with my final solution:
var daySets = [Int: [CGPoint]]()
// points is the full array of (x: dayTimeInDecimal, y: value)
for i in points {
let day = Int(i.x)
daySets[day] = (daySets[day] ?? []) + [i]
}
let meanPointsEachDay = daySets.map{ (key, value) -> CGPoint in
let count = CGFloat(value.count)
let sumPoint = value.reduce(CGPoint.zero, {CGPoint(x: $0.x + $1.x, y: $0.y + $1.y)})
return CGPoint(x: sumPoint.x/count, y: sumPoint.y/count)
}
// must be sorted by 'day'!!!
let arrA0 = [2.45, 2.75, 2.9, 3.1, 3.2, 3.3]
// associated values
let arrA1 = [145.0, 150.0, 160.0, 245.0, 250.0, 260.0]
let arr = Array(zip(arrA0, arrA1))
// now i have an array of tuples, where tuple.0 is key and tuple.1 etc. is associated value
// you can expand the tuple for as much associated values, as you want
print(arr)
// [(2.45, 145.0), (2.75, 150.0), (2.9, 160.0), (3.1, 245.0), (3.2, 250.0), (3.3, 260.0)]
// now i can perform my 'calculations' the most effective way
var res:[Int:(Double,Double)] = [:]
// sorted set of Int 'day' values
let set = Set(arr.map {Int($0.0)}).sort()
// for two int values the sort is redundant, but
// don't be depend on that!
print(set)
// [2, 3]
var g = 0
var j = 0
set.forEach { (i) -> () in
var sum1 = 0.0
var sum2 = 0.0
var t = true
while t && g < arr.count {
let v1 = arr[g].0
let v2 = arr[g].1
t = i == Int(v1)
if t {
g++
j++
} else {
break
}
sum1 += v1
sum2 += v2
}
res[i] = (sum1 / Double(j), sum2 / Double(j))
j = 0
}
print(res)
// [2: (2.7, 151.666666666667), 3: (3.2, 251.666666666667)]
see, that every element of your data is process only once in the 'calculation', independent from size of 'key' set size
use Swift's Double instead of CGFloat! this increase the speed too :-)
and finally what you are looking for
if let (day, value) = res[2] {
print(day, value) // 2.7 151.666666666667
}
so I'm creating a project and for the background I want stars randomly appearing and dissapearing from the screen.
I have the stars randomly appearing, but they just stay on the screen and eventually fill the whole screen with stars. I would like for it to start removing 1 star and add another when there is 100 stars on screen. or even for there to be 100 stars on screen at the beginning and the timer just removes and adds 1 star each time.
So I am adding mystar randomly on the stage at random times, just need to get 1 removed, at the same time as they are being added so they do not completely cover my screemn.
var myTimer:Timer = new Timer(2,0 );
var randNum = Math.round( 500 + Math.random() * 4000 );
myTimer.addEventListener(TimerEvent.TIMER, fasttimerListener);
function fasttimerListener (e:TimerEvent):void{
var timerObj:Timer = e.target as Timer;
randNum = Math.round( Math.random() * 100 );
timerObj.delay = randNum;
var mystar:smallstar = new smallstar();
mystar.x = Math.random() * stage.stageWidth;
mystar.y = Math.random() * stage.stageHeight;
addChild(mystar);
}
myTimer.start();
please help, I think I might have to use an array? but not great at as3.
thanks in advance.
Here is the code I came up after trying to solve your problam, and yes I used Array:
var myTimer:Timer = new Timer(2,0 );
var randNum = Math.round( 500 + Math.random() * 4000 );
var starsCount:int = 100; //stars number in stage
var st:int=0; //star inde number in Array
//---------------------------------------------------------------
//creating an Array of 100 mc stars
//---------------------------------------------------------------
var starsArray:Array=[]; //Array of movieclips smallstar
//using for loop to create 100 stars and put them in Array
for(var star:int=0; star<starsCount; star++){
var mystar:smallstar = new smallstar();
//puts mc smallstar on Array
starsArray.push(mystar);
}
myTimer.addEventListener(TimerEvent.TIMER, fasttimerListener);
function fasttimerListener (e:TimerEvent):void{
var timerObj:Timer = e.target as Timer;
randNum = Math.round( Math.random() * 100 );
timerObj.delay = randNum;
starsArray[st].x = Math.random() * stage.stageWidth;
starsArray[st].y = Math.random() * stage.stageHeight;
addChild(starsArray[st]);
st++; //changing star index on array
//doesnt let st to be bigger than Array length
if (st>=starsArray.length) st = 0;
}
myTimer.start();
Hope it helps.