Skip to content

Instantly share code, notes, and snippets.

@kiichi
Last active August 29, 2015 14:26
Show Gist options
  • Select an option

  • Save kiichi/224ff7b1988935fa9bd8 to your computer and use it in GitHub Desktop.

Select an option

Save kiichi/224ff7b1988935fa9bd8 to your computer and use it in GitHub Desktop.
SphericalGeometry class conversion from PHP (alpha)
// Converted from http://tubalmartin.github.io/spherical-geometry-php/
// Test results look same but doesn't look like what google map does.
// Don't assume it's perfect.
/*
var poly = new List<LatLng> {
new LatLng(47.9899216674142,61.171875),
new LatLng(50.2893392532918,68.90625),
new LatLng(52.9089020477703,78.046875),
new LatLng(56.5594824837622,95.625),
new LatLng(45.5832897560063,93.515625),
new LatLng(49.3823727870096,101.953125),
new LatLng(38.272688535981,94.21875),
new LatLng(34.8859309407532,80.15625),
new LatLng(32.5468131735152,57.65625),
new LatLng(43.0688877741696,52.03125)
};
double area = SphericalGeometry.computeArea (poly);
Console.WriteLine("google one deal with dent: " + area/1000000 + " km^2");
*/
using System;
using System.Collections.Generic;
namespace DistanceAreaCalc {
public class SphericalGeometry {
// This is original copyright. and this C# code was Converted by me.
/*!
* Spherical Geometry PHP Library v1.1.1
* http://tubalmartin.github.com/spherical-geometry-php/
*
* Copyright 2012, Túbal Martín
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* This code is a port of some classes from the Google Maps Javascript API version 3.x
*/
/**
* Static class SphericalGeometry
* Utility functions for computing geodesic angles, distances and areas.
*/
public static double EQUALS_MARGIN_ERROR = 1.0E-9;
// Earth's radius (at the Ecuator) of 6378137 meters.
public static double EARTH_RADIUS = 6378137;
public static double getEarthRadius()
{
return EARTH_RADIUS;
}
public static double deg2rad(double angle)
{
return Math.PI * angle / 180.0;
}
public static double rad2deg(double angle)
{
return Math.PI * angle / 180.0;
}
/**
* Computes a bounding rectangle (LatLngBounds instance) from a point and a given radius.
* Reference: http://www.movable-type.co.uk/scripts/latlong-db.html
*
* -------------NE
* | |
* | radius|
* | o------|
* | |
* | |
* SW-------------
*
* @param object LatLng
* @param int|float radius (In meters)
*/
public static LatLngBounds computeBounds(LatLng latlng, double radius)
{
double latRadiansDistance = radius / EARTH_RADIUS;
double latDegreesDistance = rad2deg(latRadiansDistance);
double lngDegreesDistance = rad2deg(latRadiansDistance / Math.Cos(deg2rad(latlng.Lat)));
// SW point
double swLat = latlng.Lat - latDegreesDistance;
double swLng = latlng.Lng - lngDegreesDistance;
LatLng sw = new LatLng(swLat, swLng);
// NE point
double neLat = latlng.Lat + latDegreesDistance;
double neLng = latlng.Lng + lngDegreesDistance;
LatLng ne = new LatLng(neLat, neLng);
return new LatLngBounds(sw, ne);
}
public static double computeHeading(LatLng fromLatLng, LatLng toLatLng)
{
double fromLat = deg2rad(fromLatLng.Lat);
double toLat = deg2rad(toLatLng.Lat);
double lng = deg2rad(toLatLng.Lng) - deg2rad(fromLatLng.Lng);
return wrapLongitude(rad2deg(Math.Atan2(Math.Sin(lng) * Math.Cos(toLat), Math.Cos(fromLat)
* Math.Sin(toLat) - Math.Sin(fromLat) * Math.Cos(toLat) * Math.Cos(lng))));
}
public static LatLng computeOffset(LatLng fromLatLng, double distance, double heading)
{
distance /= EARTH_RADIUS;
heading = deg2rad(heading);
double fromLat = deg2rad(fromLatLng.Lat);
double cosDistance = Math.Cos(distance);
double sinDistance = Math.Sin(distance);
double sinFromLat = Math.Sin(fromLat);
double cosFromLat = Math.Cos(fromLat);
double sc = cosDistance * sinFromLat + sinDistance * cosFromLat * Math.Cos(heading);
double lat = rad2deg(Math.Asin(sc));
double lng = rad2deg(deg2rad(fromLatLng.Lng) + Math.Atan2(sinDistance * cosFromLat
* Math.Sin(heading), cosDistance - sinFromLat * sc));
return new LatLng(lat, lng);
}
public static LatLng interpolate(LatLng fromLatLng, LatLng toLatLng, double fraction)
{
double radFromLat = deg2rad(fromLatLng.Lat);
double radFromLng = deg2rad(fromLatLng.Lng);
double radToLat = deg2rad(toLatLng.Lat);
double radToLng = deg2rad(toLatLng.Lng);
double cosFromLat = Math.Cos(radFromLat);
double cosToLat = Math.Cos(radToLat);
double radDist = _computeDistanceInRadiansBetween(fromLatLng, toLatLng);
double sinRadDist = Math.Sin(radDist);
if (sinRadDist < 1.0E-6)
{
return new LatLng(fromLatLng.Lat, fromLatLng.Lng);
}
double a = Math.Sin((1 - fraction) * radDist) / sinRadDist;
double b = Math.Sin(fraction * radDist) / sinRadDist;
double c = a * cosFromLat * Math.Cos(radFromLng) + b * cosToLat * Math.Cos(radToLng);
double d = a * cosFromLat * Math.Sin(radFromLng) + b * cosToLat * Math.Sin(radToLng);
double lat = rad2deg(Math.Atan2(a * Math.Sin(radFromLat) + b * Math.Sin(radToLat), Math.Sqrt(Math.Pow(c,2) + Math.Pow(d,2))));
double lng = rad2deg(Math.Atan2(d, c));
return new LatLng(lat, lng);
}
public static double computeDistanceBetween(LatLng LatLng1, LatLng LatLng2)
{
return _computeDistanceInRadiansBetween(LatLng1, LatLng2) * EARTH_RADIUS;
}
public static double computeLength(List<LatLng> LatLngsArray)
{
double length = 0;
for (int i = 0, l = LatLngsArray.Count - 1; i < l; ++i)
{
length += computeDistanceBetween(LatLngsArray[i], LatLngsArray[i + 1]);
}
return length;
}
public static double computeArea(List<LatLng> LatLngsArray)
{
return Math.Abs(computeSignedArea(LatLngsArray, false));
}
public static double computeSignedArea(List<LatLng> LatLngsArray, bool signed = true)
{
if (LatLngsArray.Count == 0 || LatLngsArray.Count < 3) return 0;
double e = 0;
double r2 = Math.Pow(EARTH_RADIUS, 2);
for (int i = 1, l = LatLngsArray.Count - 1; i < l; ++i)
{
e += _computeSphericalExcess(LatLngsArray[0], LatLngsArray[i], LatLngsArray[i + 1], signed);
}
return e * r2;
}
// Clamp latitude
public static double clampLatitude(double lat)
{
return Math.Min(Math.Max(lat, -90), 90);
}
// Wrap longitude
public static double wrapLongitude(double lng)
{
return lng == 180 ? lng : ((((lng - -180)%360.0) + 360)%360.0) + -180;
}
/**
* Computes the great circle distance (in radians) between two points.
* Uses the Haversine formula.
*/
protected static double _computeDistanceInRadiansBetween(LatLng LatLng1, LatLng LatLng2)
{
double p1RadLat = deg2rad(LatLng1.Lat);
double p1RadLng = deg2rad(LatLng1.Lng);
double p2RadLat = deg2rad(LatLng2.Lat);
double p2RadLng = deg2rad(LatLng2.Lng);
return 2 * Math.Asin( Math.Sqrt(Math.Pow(Math.Sin((p1RadLat - p2RadLat) / 2), 2) + Math.Cos(p1RadLat)
* Math.Cos(p2RadLat) * Math.Pow(Math.Sin((p1RadLng - p2RadLng) / 2), 2)));
}
/**
* Computes the spherical excess.
* Uses L'Huilier's Theorem.
*/
protected static double _computeSphericalExcess(LatLng LatLng1, LatLng LatLng2, LatLng LatLng3, bool signed)
{
LatLng[] latLngsArray = new LatLng[]{LatLng1, LatLng2, LatLng3, LatLng1};
double[] distances = new double[3];
double sumOfDistances = 0;
for (int i = 0; i < 3; ++i)
{
distances[i] = _computeDistanceInRadiansBetween(latLngsArray[i], latLngsArray[i + 1]);
sumOfDistances += distances[i];
}
double semiPerimeter = sumOfDistances / 2;
double tan = Math.Tan(semiPerimeter / 2);
for (int i = 0; i < 3; ++i)
{
tan *= Math.Tan((semiPerimeter - distances[i]) / 2);
}
double sphericalExcess = 4 * Math.Atan( Math.Sqrt( Math.Abs(tan)));
if (!signed)
{
return sphericalExcess;
}
// Negative or positive sign?
//latLngsArray.RemoveAt(latLngsArray.Length-1);
Array.Copy (latLngsArray, latLngsArray, latLngsArray.Length - 1);
double[,] v = new double[3,3];
for (int i = 0; i < 3; ++i)
{
LatLng LatLng = latLngsArray[i];
double lat = deg2rad(LatLng.Lat);
double lng = deg2rad(LatLng.Lng);
//v[i] = array();
v[i,0] = Math.Cos(lat) * Math.Cos(lng);
v[i,1] = Math.Cos(lat) * Math.Sin(lng);
v[i,2] = Math.Sin(lat);
}
double sign = (v[0,0] * v[1,1] * v[2,2]
+ v[1,0] * v[2,1] * v[0,2]
+ v[2,0] * v[0,1] * v[1,2]
- v[0,0] * v[2,1] * v[1,2]
- v[1,0] * v[0,1] * v[2,2]
- v[2,0] * v[1,1] * v[0,2]) > 0 ? 1 : -1;
return sphericalExcess * sign;
}
}
public class LatLng
{
protected double _lat;
protected double _lng;
public double Lat{
get {
return this._lat;
}
}
public double Lng{
get {
return this._lng;
}
}
public LatLng(double lat, double lng, bool noWrap = false)
{
lat = (float) lat;
lng = (float) lng;
if (noWrap == false)
{
lat = SphericalGeometry.clampLatitude(lat);
lng = SphericalGeometry.wrapLongitude(lng);
}
this._lat = lat;
this._lng = lng;
}
public double getLat()
{
return this._lat;
}
public double getLng()
{
return this._lng;
}
public bool equals(LatLng LatLng)
{
if (LatLng == null) {
return false;
}
return (Math.Abs(this._lat-LatLng.Lat) + Math.Abs(this._lng-LatLng.Lng)) <= SphericalGeometry.EQUALS_MARGIN_ERROR;
}
public string toString()
{
return "("+this._lat+", "+ this._lng +")";
}
public string toUrlValue(int precision = 6)
{
precision = (int) precision;
return Math.Round(this._lat, precision) +","+ Math.Round(this._lng, precision);
}
}
public class LatLngBounds
{
LatBounds _LatBounds;
LngBounds _LngBounds;
/**
* LatLngSw South West LatLng object
* LatLngNe North East LatLng object
*/
public LatLngBounds(LatLng LatLngSw = null, LatLng LatLngNe = null)
{
if (LatLngSw != null)
{
LatLngNe = (LatLngNe!=null) ? LatLngSw : LatLngNe;
double sw = SphericalGeometry.clampLatitude(LatLngSw.Lat);
double ne = SphericalGeometry.clampLatitude(LatLngNe.Lat);
this._LatBounds = new LatBounds(sw, ne);
sw = LatLngSw.Lng;
ne = LatLngNe.Lng;
if (360 <= ne - sw)
{
this._LngBounds = new LngBounds(-180, 180);
}
else
{
sw = SphericalGeometry.wrapLongitude(sw);
ne = SphericalGeometry.wrapLongitude(ne);
this._LngBounds = new LngBounds(sw, ne);
}
}
else
{
this._LatBounds = new LatBounds(1, -1);
this._LngBounds = new LngBounds(180, -180);
}
}
public LatBounds getLatBounds()
{
return this._LatBounds;
}
public LngBounds getLngBounds()
{
return this._LngBounds;
}
public LatLng getCenter()
{
return new LatLng(this._LatBounds.getMidpoint(), this._LngBounds.getMidpoint());
}
public bool isEmpty()
{
return this._LatBounds.isEmpty() || this._LngBounds.isEmpty();
}
public LatLng getSouthWest()
{
return new LatLng(this._LatBounds.getSw(), this._LngBounds.getSw(), true);
}
public LatLng getNorthEast()
{
return new LatLng(this._LatBounds.getNe(), this._LngBounds.getNe(), true);
}
public LatLng toSpan()
{
double lat = this._LatBounds.isEmpty() ? 0 : this._LatBounds.getNe() - this._LatBounds.getSw();
double lng = this._LngBounds.isEmpty()
? 0
: (this._LngBounds.getSw() > this._LngBounds.getNe()
? 360 - (this._LngBounds.getSw() - this._LngBounds.getNe())
: this._LngBounds.getNe() - this._LngBounds.getSw());
return new LatLng(lat, lng, true);
}
public string toString()
{
return "("+ this.getSouthWest().toString() +", "+ this.getNorthEast().toString() +")";
}
public string toUrlValue(int precision = 6)
{
return this.getSouthWest().toUrlValue(precision) +","+
this.getNorthEast().toUrlValue(precision);
}
public bool equals(LatLngBounds LatLngBounds)
{
return !(LatLngBounds!=null)
? false
: this._LatBounds.equals(LatLngBounds.getLatBounds())
&& this._LngBounds.equals(LatLngBounds.getLngBounds());
}
public bool intersects(LatLngBounds latlngBounds)
{
return this._LatBounds.intersects(latlngBounds.getLatBounds())
&& this._LngBounds.intersects(latlngBounds.getLngBounds());
}
public LatLngBounds union(LatLngBounds LatLngBounds)
{
this.extend(LatLngBounds.getSouthWest());
this.extend(LatLngBounds.getNorthEast());
return this;
}
public bool contains(LatLng LatLng)
{
return this._LatBounds.contains(LatLng.Lat)
&& this._LngBounds.contains(LatLng.Lng);
}
public LatLngBounds extend(LatLng LatLng)
{
this._LatBounds.extend(LatLng.Lat);
this._LngBounds.extend(LatLng.Lng);
return this;
}
}
// DO NOT USE THE CLASSES BELOW DIRECTLY
public class LatBounds
{
protected double _swLat;
protected double _neLat;
public LatBounds(double swLat,double neLat)
{
this._swLat = swLat;
this._neLat = neLat;
}
public double getSw()
{
return this._swLat;
}
public double getNe()
{
return this._neLat;
}
public double getMidpoint()
{
return (this._swLat + this._neLat) / 2;
}
public bool isEmpty()
{
return this._swLat > this._neLat;
}
public bool intersects(LatBounds LatBounds)
{
return this._swLat <= LatBounds.getSw()
? LatBounds.getSw() <= this._neLat && LatBounds.getSw() <= LatBounds.getNe()
: this._swLat <= LatBounds.getNe() && this._swLat <= this._neLat;
}
public bool equals(LatBounds LatBounds)
{
return this.isEmpty()
? LatBounds.isEmpty()
: Math.Abs(LatBounds.getSw() - this._swLat)
+ Math.Abs(this._neLat - LatBounds.getNe()) <= SphericalGeometry.EQUALS_MARGIN_ERROR;
}
public bool contains(double lat)
{
return lat >= this._swLat && lat <= this._neLat;
}
public void extend(double lat)
{
if (this.isEmpty())
{
this._neLat = this._swLat = lat;
}
else if (lat < this._swLat)
{
this._swLat = lat;
}
else if (lat > this._neLat)
{
this._neLat = lat;
}
}
}
public class LngBounds
{
protected double _swLng;
protected double _neLng;
public LngBounds(double swLng, double neLng)
{
swLng = swLng == -180 && neLng != 180 ? 180 : swLng;
neLng = neLng == -180 && swLng != 180 ? 180 : neLng;
this._swLng = swLng;
this._neLng = neLng;
}
public double getSw()
{
return this._swLng;
}
public double getNe()
{
return this._neLng;
}
public double getMidpoint()
{
double midPoint = (this._swLng + this._neLng) / 2;
if (this._swLng > this._neLng)
{
midPoint = SphericalGeometry.wrapLongitude(midPoint + 180);
}
return midPoint;
}
public bool isEmpty()
{
return this._swLng - this._neLng == 360;
}
public bool intersects(LngBounds LngBounds)
{
if (this.isEmpty() || LngBounds.isEmpty())
{
return false;
}
else if (this._swLng > this._neLng)
{
return LngBounds.getSw() > LngBounds.getNe()
|| LngBounds.getSw() <= this._neLng
|| LngBounds.getNe() >= this._swLng;
}
else if (LngBounds.getSw() > LngBounds.getNe())
{
return LngBounds.getSw() <= this._neLng || LngBounds.getNe() >= this._swLng;
}
else
{
return LngBounds.getSw() <= this._neLng && LngBounds.getNe() >= this._swLng;
}
}
public bool equals(LngBounds LngBounds)
{
return this.isEmpty ()
? LngBounds.isEmpty ()
: (Math.Abs (LngBounds.getSw () - this._swLng) % 360.0f
+ Math.Abs (LngBounds.getNe () - this._neLng) % 360.0f)
<= SphericalGeometry.EQUALS_MARGIN_ERROR;
}
public bool contains(double lng)
{
lng = lng == -180 ? 180 : lng;
return this._swLng > this._neLng
? (lng >= this._swLng || lng <= this._neLng) && !this.isEmpty()
: lng >= this._swLng && lng <= this._neLng;
}
public void extend(double lng)
{
if (this.contains(lng))
{
return;
}
if (this.isEmpty())
{
this._swLng = this._neLng = lng;
}
else
{
double swLng = this._swLng - lng;
swLng = swLng >= 0 ? swLng : this._swLng + 180 - (lng - 180);
double neLng = lng - this._neLng;
neLng = neLng >= 0 ? neLng : lng + 180 - (this._neLng - 180);
if (swLng < neLng)
{
this._swLng = lng;
}
else
{
this._neLng = lng;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment