datenspuren: geo-aware Imbißmöglichkeiten
parent
f202c690b7
commit
5d20c3c6f4
@ -0,0 +1,141 @@
|
||||
var currentPosition = {
|
||||
lat: 51.0420162,
|
||||
lon: 13.7976983
|
||||
}
|
||||
var geoListeners = []
|
||||
|
||||
function POI(info) {
|
||||
this.info = info
|
||||
}
|
||||
|
||||
POI.prototype.getDistance = function() {
|
||||
return WGS84Util.distanceBetween({
|
||||
coordinates: [this.info.lon, this.info.lat]
|
||||
}, {
|
||||
coordinates: [currentPosition.lon, currentPosition.lat]
|
||||
})
|
||||
}
|
||||
|
||||
POI.prototype.makeArrowEl = function() {
|
||||
var el = $('<div class="geo-arrow"><p class="arrow"><span>➢</span></p><p class="dist"></p></div>')
|
||||
|
||||
var update = function() {
|
||||
var bearing = -180.0 * Math.atan2(
|
||||
this.info.lat - currentPosition.lat,
|
||||
this.info.lon - currentPosition.lon
|
||||
) / Math.PI
|
||||
el.find('.arrow span').css('transform', 'rotate(' + (bearing - (currentPosition.heading || 0)) + 'deg)')
|
||||
if (typeof currentPosition.heading === 'number')
|
||||
el.find('.arrow span').text("➡")
|
||||
|
||||
el.find('.dist').text(formatDistance(this.getDistance()))
|
||||
}.bind(this)
|
||||
|
||||
// Initialize:
|
||||
update()
|
||||
// hook onGeo listener
|
||||
geoListeners.push(update)
|
||||
|
||||
return el
|
||||
}
|
||||
|
||||
function onGeo(geo) {
|
||||
console.log("onGeo", geo)
|
||||
currentPosition = {
|
||||
lat: geo.coords.latitude,
|
||||
lon: geo.coords.longitude,
|
||||
heading: geo.heading
|
||||
}
|
||||
// call listeners
|
||||
geoListeners.forEach(function(f) {
|
||||
f(currentPosition)
|
||||
})
|
||||
}
|
||||
|
||||
function formatDistance(x) {
|
||||
if (x >= 1000) {
|
||||
return (Math.round(x / 100) / 10) + " km"
|
||||
} else {
|
||||
return Math.round(x) + " m"
|
||||
}
|
||||
}
|
||||
|
||||
function makeLink(link) {
|
||||
var a = $('<a></a>')
|
||||
a.attr('href', link.indexOf("://") < 0 ? "http://" + link : link)
|
||||
a.text(link.replace(/^\w+?:\/\//, "").replace(/\/+$/, ""))
|
||||
return a
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(function(geo) {
|
||||
onGeo(geo)
|
||||
navigator.geolocation.watchPosition(onGeo, null, {
|
||||
enableHighAccuracy: true
|
||||
})
|
||||
})
|
||||
|
||||
var h3 = $('<h3 id="foodlocator">Imbißmöglichkeiten anzeigen</h3>')
|
||||
$('article').append(h3)
|
||||
|
||||
var ran = false
|
||||
h3.click(function() {
|
||||
if (ran) return
|
||||
ran = true
|
||||
|
||||
h3.addClass('expanded')
|
||||
h3.text("Lade Imbißmöglichkeiten...")
|
||||
|
||||
$.ajax({
|
||||
url: "pois.json",
|
||||
success: function(json) {
|
||||
var pois = json.elements.map(function(info) {
|
||||
return new POI(info)
|
||||
}).filter(function(poi) {
|
||||
return poi.getDistance() <= 1000
|
||||
}).sort(function(a, b) {
|
||||
return a.getDistance() - b.getDistance()
|
||||
})
|
||||
|
||||
var dl = $('<dl id="food"></dl>')
|
||||
pois.forEach(function(poi) {
|
||||
if (!poi.info.tags.name) {
|
||||
console.warn("No name:", poi.info)
|
||||
return // Skip
|
||||
}
|
||||
|
||||
var dt = $('<dt><a></a></dt>')
|
||||
dt.find('a').
|
||||
attr('href', "https://www.openstreetmap.org/" + poi.info.type + "/" + poi.info.id).
|
||||
text(poi.info.tags.name)
|
||||
|
||||
if (poi.info.tags['addr:street'] && poi.info.tags['addr:housenumber']) {
|
||||
var addr = $('<span class="addr"></span>')
|
||||
addr.text(poi.info.tags['addr:street'] + " " +
|
||||
poi.info.tags['addr:housenumber'])
|
||||
dt.append(addr)
|
||||
}
|
||||
if (poi.info.tags.website) {
|
||||
var addr = $('<span class="website"></span>')
|
||||
addr.append(makeLink(poi.info.tags.website))
|
||||
dt.append(addr)
|
||||
}
|
||||
dt.prepend(poi.makeArrowEl())
|
||||
dl.append(dt)
|
||||
|
||||
if (poi.info.tags.opening_hours) {
|
||||
poi.info.tags.opening_hours.split(/\s*;\s*/g).forEach(function(range) {
|
||||
var dd = $('<dd></dd>')
|
||||
dd.text(range)
|
||||
dl.append(dd)
|
||||
})
|
||||
}
|
||||
})
|
||||
$('article').append(dl)
|
||||
h3.text("Imbißmöglichkeiten in der Umgebung")
|
||||
$('article').append("<p style='margin-top: 1.5em'>Quelle: OpenStreetMap. Stündlich aktualisiert mithilfe Overpass Turbo.</p>")
|
||||
},
|
||||
error: function() {
|
||||
h3.text("Fehler! Versuch z.B. osm24.eu stattdessen.")
|
||||
}
|
||||
})
|
||||
})
|
@ -0,0 +1,251 @@
|
||||
/** @fileOverview Geographic coordinate utilities using WGS84 datum
|
||||
* @author cs_brandt
|
||||
* @date 02/25/2013
|
||||
*/
|
||||
|
||||
|
||||
/** @module wgs84-util */
|
||||
var WGS84Util = window.WGS84Util = {};
|
||||
|
||||
// Semi-Major Axis (Equatorial Radius)
|
||||
var SEMI_MAJOR_AXIS = 6378137.0;
|
||||
// First Eccentricity Squared
|
||||
var ECC_SQUARED = 0.006694380004260827;
|
||||
|
||||
/**
|
||||
* From: Haversine formula - RW Sinnott, "Virtues of the Haversine",
|
||||
* Sky and Telescope, vol 68, no 2, 1984
|
||||
*
|
||||
* @param {object} coordA GeoJSON point
|
||||
* @param {object} coordB GeoJSON point
|
||||
* @return {number} the distance from this point to the supplied point, in km
|
||||
* (using Haversine formula)
|
||||
*
|
||||
*/
|
||||
WGS84Util.distanceBetween = function(coordA, coordB) {
|
||||
var lat1 = this.degToRad(coordA.coordinates[1]), lon1 = this.degToRad(coordA.coordinates[0]);
|
||||
var lat2 = this.degToRad(coordB.coordinates[1]), lon2 = this.degToRad(coordB.coordinates[0]);
|
||||
var dLat = lat2 - lat1;
|
||||
var dLon = lon2 - lon1;
|
||||
|
||||
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
||||
Math.cos(lat1) * Math.cos(lat2) *
|
||||
Math.sin(dLon/2) * Math.sin(dLon/2);
|
||||
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||
var d = SEMI_MAJOR_AXIS * c;
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the destination point from this point having travelled the given distance (in m) on the
|
||||
* given initial bearing (bearing may vary before destination is reached)
|
||||
*
|
||||
* see http://williams.best.vwh.net/avform.htm#LL
|
||||
*
|
||||
* @param {object} coordA GeoJSON point
|
||||
* @param {Number} brng: Initial bearing in degrees
|
||||
* @param {Number} dist: Distance in m
|
||||
*
|
||||
* @returns {object} GeoJSON destination point
|
||||
*/
|
||||
WGS84Util.destinationPoint = function(coordA, brng, dist) {
|
||||
dist = typeof(dist) == 'number' ? dist : typeof(dist) == 'string' && dist.trim() != '' ? +dist : NaN;
|
||||
dist = dist / SEMI_MAJOR_AXIS; // convert dist to angular distance in radians
|
||||
brng = this.degToRad(brng); //
|
||||
var lat1 = this.degToRad(coordA.coordinates[1]), lon1 = this.degToRad(coordA.coordinates[0]);
|
||||
|
||||
var lat2 = Math.asin( Math.sin(lat1) * Math.cos(dist) +
|
||||
Math.cos(lat1) * Math.sin(dist) * Math.cos(brng) );
|
||||
var lon2 = lon1 + Math.atan2(Math.sin(brng) * Math.sin(dist) * Math.cos(lat1),
|
||||
Math.cos(dist) - Math.sin(lat1) * Math.sin(lat2));
|
||||
lon2 = (lon2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
|
||||
|
||||
return {
|
||||
"type": "Point",
|
||||
"coordinates": [parseFloat(this.radToDeg(lon2).toFixed(10)), parseFloat(this.radToDeg(lat2).toFixed(10))]
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Conversion from degrees to radians.
|
||||
*
|
||||
* @param {number} deg the angle in degrees.
|
||||
* @return {number} the angle in radians.
|
||||
*/
|
||||
WGS84Util.degToRad = function(deg) {
|
||||
return (deg * (Math.PI / 180.0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Conversion from radians to degrees.
|
||||
*
|
||||
* @param {number} rad the angle in radians.
|
||||
* @return {number} the angle in degrees.
|
||||
*/
|
||||
WGS84Util.radToDeg = function(rad) {
|
||||
return (180.0 * (rad / Math.PI));
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a set of Longitude and Latitude co-ordinates to UTM
|
||||
* using the WGS84 ellipsoid.
|
||||
*
|
||||
* @param {object} ll Object literal with lat and lon properties
|
||||
* representing the WGS84 coordinate to be converted.
|
||||
* @return {object} Object literal containing the UTM value with easting,
|
||||
* northing, zoneNumber and zoneLetter properties, and an optional
|
||||
* accuracy property in digits. Returns null if the conversion failed.
|
||||
*/
|
||||
WGS84Util.LLtoUTM = function(ll) {
|
||||
var Lat = ll.coordinates[1];
|
||||
var Long = ll.coordinates[0];
|
||||
var k0 = 0.9996;
|
||||
var LongOrigin;
|
||||
var eccPrimeSquared;
|
||||
var N, T, C, A, M;
|
||||
var LatRad = this.degToRad(Lat);
|
||||
var LongRad = this.degToRad(Long);
|
||||
var LongOriginRad;
|
||||
var ZoneNumber;
|
||||
var zoneLetter = 'N';
|
||||
// (int)
|
||||
ZoneNumber = Math.floor((Long + 180) / 6) + 1;
|
||||
|
||||
//Make sure the longitude 180.00 is in Zone 60
|
||||
if (Long === 180) {
|
||||
ZoneNumber = 60;
|
||||
}
|
||||
|
||||
// Special zone for Norway
|
||||
if (Lat >= 56.0 && Lat < 64.0 && Long >= 3.0 && Long < 12.0) {
|
||||
ZoneNumber = 32;
|
||||
}
|
||||
|
||||
// Special zones for Svalbard
|
||||
if (Lat >= 72.0 && Lat < 84.0) {
|
||||
if (Long >= 0.0 && Long < 9.0) {
|
||||
ZoneNumber = 31;
|
||||
} else if (Long >= 9.0 && Long < 21.0) {
|
||||
ZoneNumber = 33;
|
||||
} else if (Long >= 21.0 && Long < 33.0) {
|
||||
ZoneNumber = 35;
|
||||
} else if (Long >= 33.0 && Long < 42.0) {
|
||||
ZoneNumber = 37;
|
||||
}
|
||||
}
|
||||
|
||||
LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3; //+3 puts origin
|
||||
// in middle of
|
||||
// zone
|
||||
LongOriginRad = this.degToRad(LongOrigin);
|
||||
|
||||
eccPrimeSquared = (ECC_SQUARED) / (1 - ECC_SQUARED);
|
||||
|
||||
N = SEMI_MAJOR_AXIS / Math.sqrt(1 - ECC_SQUARED * Math.sin(LatRad) * Math.sin(LatRad));
|
||||
T = Math.tan(LatRad) * Math.tan(LatRad);
|
||||
C = eccPrimeSquared * Math.cos(LatRad) * Math.cos(LatRad);
|
||||
A = Math.cos(LatRad) * (LongRad - LongOriginRad);
|
||||
|
||||
M = SEMI_MAJOR_AXIS * ((1 - ECC_SQUARED / 4 - 3 * ECC_SQUARED * ECC_SQUARED / 64 - 5 * ECC_SQUARED * ECC_SQUARED * ECC_SQUARED / 256) * LatRad - (3 * ECC_SQUARED / 8 + 3 * ECC_SQUARED * ECC_SQUARED / 32 + 45 * ECC_SQUARED * ECC_SQUARED * ECC_SQUARED / 1024) * Math.sin(2 * LatRad) + (15 * ECC_SQUARED * ECC_SQUARED / 256 + 45 * ECC_SQUARED * ECC_SQUARED * ECC_SQUARED / 1024) * Math.sin(4 * LatRad) - (35 * ECC_SQUARED * ECC_SQUARED * ECC_SQUARED / 3072) * Math.sin(6 * LatRad));
|
||||
|
||||
var UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6.0 + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120.0) + 500000.0);
|
||||
|
||||
var UTMNorthing = (k0 * (M + N * Math.tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24.0 + (61 - 58 * T + T * T + 600 * C - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720.0)));
|
||||
|
||||
if (Lat < 0.0) {
|
||||
UTMNorthing += 10000000.0; //10000000 meter offset for
|
||||
// southern hemisphere
|
||||
zoneLetter = 'S';
|
||||
}
|
||||
|
||||
return {"type": "Feature", "geometry": {"type": "Point", "coordinates": [parseFloat(UTMEasting.toFixed(1)), parseFloat(UTMNorthing.toFixed(1))]}, "properties": {"zoneLetter": zoneLetter, "zoneNumber": ZoneNumber}};
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts UTM coords to lat/long, using the WGS84 ellipsoid. This is a convenience
|
||||
* class where the Zone can be specified as a single string eg."60N" which
|
||||
* is then broken down into the ZoneNumber and ZoneLetter.
|
||||
*
|
||||
* @param {object} utm An object literal with northing, easting, zoneNumber
|
||||
* and zoneLetter properties. If an optional accuracy property is
|
||||
* provided (in meters), a bounding box will be returned instead of
|
||||
* latitude and longitude.
|
||||
* @return {object} An object literal containing either lat and lon values
|
||||
* (if no accuracy was provided), or top, right, bottom and left values
|
||||
* for the bounding box calculated according to the provided accuracy.
|
||||
* Returns null if the conversion failed.
|
||||
*/
|
||||
WGS84Util.UTMtoLL = function(utm) {
|
||||
var UTMNorthing = utm.geometry.coordinates[1];
|
||||
var UTMEasting = utm.geometry.coordinates[0];
|
||||
var zoneLetter = utm.properties.zoneLetter;
|
||||
var zoneNumber = utm.properties.zoneNumber;
|
||||
// check the ZoneNummber is valid
|
||||
if (zoneNumber < 0 || zoneNumber > 60) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var k0 = 0.9996;
|
||||
var eccPrimeSquared;
|
||||
var e1 = (1 - Math.sqrt(1 - ECC_SQUARED)) / (1 + Math.sqrt(1 - ECC_SQUARED));
|
||||
var N1, T1, C1, R1, D, M;
|
||||
var LongOrigin;
|
||||
var mu, phi1Rad;
|
||||
|
||||
// remove 500,000 meter offset for longitude
|
||||
var x = UTMEasting - 500000.0;
|
||||
var y = UTMNorthing;
|
||||
|
||||
// We must know somehow if we are in the Northern or Southern
|
||||
// hemisphere, this is the only time we use the letter So even
|
||||
// if the Zone letter isn't exactly correct it should indicate
|
||||
// the hemisphere correctly
|
||||
if (zoneLetter === 'S') {
|
||||
y -= 10000000.0; // remove 10,000,000 meter offset used
|
||||
// for southern hemisphere
|
||||
}
|
||||
|
||||
// There are 60 zones with zone 1 being at West -180 to -174
|
||||
LongOrigin = (zoneNumber - 1) * 6 - 180 + 3; // +3 puts origin
|
||||
// in middle of
|
||||
// zone
|
||||
eccPrimeSquared = (ECC_SQUARED) / (1 - ECC_SQUARED);
|
||||
|
||||
M = y / k0;
|
||||
mu = M / (SEMI_MAJOR_AXIS * (1 - ECC_SQUARED / 4 - 3 * ECC_SQUARED * ECC_SQUARED / 64 - 5 * ECC_SQUARED * ECC_SQUARED * ECC_SQUARED / 256));
|
||||
|
||||
phi1Rad = mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * Math.sin(2 * mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * Math.sin(4 * mu) + (151 * e1 * e1 * e1 / 96) * Math.sin(6 * mu);
|
||||
// double phi1 = ProjMath.radToDeg(phi1Rad);
|
||||
N1 = SEMI_MAJOR_AXIS / Math.sqrt(1 - ECC_SQUARED * Math.sin(phi1Rad) * Math.sin(phi1Rad));
|
||||
T1 = Math.tan(phi1Rad) * Math.tan(phi1Rad);
|
||||
C1 = eccPrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
|
||||
R1 = SEMI_MAJOR_AXIS * (1 - ECC_SQUARED) / Math.pow(1 - ECC_SQUARED * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5);
|
||||
D = x / (N1 * k0);
|
||||
|
||||
var lat = phi1Rad - (N1 * Math.tan(phi1Rad) / R1) * (D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720);
|
||||
lat = this.radToDeg(lat);
|
||||
|
||||
var lon = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) * D * D * D * D * D / 120) / Math.cos(phi1Rad);
|
||||
lon = LongOrigin + this.radToDeg(lon);
|
||||
|
||||
var result = { "type": "Point", "coordinates": [] };
|
||||
if (utm.accuracy) {
|
||||
var topRight = this.UTMtoLL({
|
||||
northing: utm.northing + utm.accuracy,
|
||||
easting: utm.easting + utm.accuracy,
|
||||
zoneLetter: utm.zoneLetter,
|
||||
zoneNumber: utm.zoneNumber
|
||||
});
|
||||
result = {
|
||||
top: topRight.lat,
|
||||
right: topRight.lon,
|
||||
bottom: lat,
|
||||
left: lon
|
||||
};
|
||||
} else {
|
||||
result.coordinates[0] = parseFloat(lon.toFixed(8));
|
||||
result.coordinates[1] = parseFloat(lat.toFixed(8));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
Loading…
Reference in New Issue