forked from Open-CT/openct-tasks
217 lines
6.3 KiB
JavaScript
217 lines
6.3 KiB
JavaScript
// Geography library
|
|
|
|
var Geography = {};
|
|
|
|
// distance calculator
|
|
Geography.DistanceCalculator = function(unit) {
|
|
|
|
function getEarthRadius() {
|
|
var earthRadius = {
|
|
'yards': 6967410,
|
|
'km': 6371,
|
|
'miles': 3959,
|
|
'metres': 6371000,
|
|
'feet': 20902231
|
|
};
|
|
return earthRadius[unit] || earthRadius['km'];
|
|
}
|
|
|
|
var r = getEarthRadius(unit)
|
|
|
|
function deg2rad(deg) {
|
|
return deg * (Math.PI / 180)
|
|
}
|
|
|
|
|
|
this.getDistance = function(point1, point2) {
|
|
var dlat = deg2rad(point2.lat - point1.lat);
|
|
var dlon = deg2rad(point2.lng - point1.lng);
|
|
var a =
|
|
Math.sin(dlat / 2) * Math.sin(dlat / 2) +
|
|
Math.cos(deg2rad(point1.lat)) * Math.cos(deg2rad(point2.lat)) *
|
|
Math.sin(dlon / 2) * Math.sin(dlon / 2);
|
|
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
return r * c;
|
|
}
|
|
|
|
|
|
this.getMidPoint = function(point1, point2) {
|
|
var lat1 = deg2rad(point1.lat);
|
|
var lng1 = deg2rad(point1.lng);
|
|
var lat2 = deg2rad(point2.lat);
|
|
var lng2 = deg2rad(point2.lng);
|
|
var dlng = lng2 - lng1;
|
|
var bx = Math.cos(lat2) * Math.cos(dlng);
|
|
var by = Math.cos(lat2) * Math.sin(dlng);
|
|
var lat = Math.atan2(
|
|
Math.sin(lat1) + Math.sin(lat2),
|
|
Math.sqrt((Math.cos(lat1) + bx) * (Math.cos(lat1) + bx) + by * by)
|
|
);
|
|
var lng = lng1 + Math.atan2(by, Math.cos(lat1) + bx);
|
|
|
|
return {
|
|
lat: lat * 180 / Math.PI,
|
|
lng: lng * 180 / Math.PI
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// map renderer 2D
|
|
Geography.Renderer2D = function(options) {
|
|
|
|
|
|
function ImageLoader(src, onLoad) {
|
|
var loaded = false;
|
|
var img = new Image();
|
|
img.src = src;
|
|
img.onload = function() {
|
|
loaded = true;
|
|
onLoad && onLoad();
|
|
}
|
|
img.onerror = function() {
|
|
console.error('Error loading image: ' + src);
|
|
}
|
|
this.get = function() {
|
|
return loaded ? img : null;
|
|
}
|
|
}
|
|
|
|
|
|
// measure text size
|
|
var span = document.createElement('span');
|
|
span.style.whiteSpace = 'nowrap';
|
|
span.style.display = 'inline';
|
|
span.style.visibility = 'hidden';
|
|
span.style.font = options.font;
|
|
if(options.display) {
|
|
document.body.append(span);
|
|
}
|
|
|
|
|
|
function getTextSize(text) {
|
|
span.innerHTML = text;
|
|
return {
|
|
width: span.offsetWidth,
|
|
height: span.offsetHeight
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function CoordinatesConverter() {
|
|
var map_lat_bottomRad = options.map_lat_bottom * Math.PI / 180;
|
|
var mapLngDelta = (options.map_lng_right - options.map_lng_left)
|
|
var worldMapWidth = ((options.width / mapLngDelta) * 360) / (2 * Math.PI)
|
|
var mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(map_lat_bottomRad)) / (1 - Math.sin(map_lat_bottomRad))))
|
|
|
|
this.x = function(lng) {
|
|
return (lng - options.map_lng_left) * (options.width / mapLngDelta);
|
|
}
|
|
|
|
this.y = function(lat) {
|
|
var latitudeRad = lat * Math.PI / 180;
|
|
return options.height - ((worldMapWidth / 2 * Math.log((1 + Math.sin(latitudeRad)) / (1 - Math.sin(latitudeRad)))) - mapOffsetY)
|
|
}
|
|
}
|
|
|
|
|
|
function rgba(colors, opacity) {
|
|
return 'rgba(' + colors.r + ',' + colors.g + ',' + colors.b + ',' + opacity + ')';
|
|
}
|
|
|
|
|
|
this.clear = function() {
|
|
var img = images.map.get();
|
|
if(img) {
|
|
context.drawImage(img, 0, 0, options.width, options.height)
|
|
} else {
|
|
context.fillStyle = rgba(options.background_color, 1);
|
|
context.fillRect(0, 0, options.width, options.height)
|
|
}
|
|
}
|
|
|
|
|
|
this.line = function(lng1, lat1, lng2, lat2, opacity) {
|
|
context.lineWidth = options.line_width;
|
|
context.strokeStyle = rgba(options.line_color, opacity);
|
|
context.beginPath();
|
|
context.moveTo(coordinates.x(lng1), coordinates.y(lat1));
|
|
context.lineTo(coordinates.x(lng2), coordinates.y(lat2));
|
|
context.stroke();
|
|
}
|
|
|
|
|
|
this.pin = function(lng, lat, label) {
|
|
var x = coordinates.x(lng);
|
|
var y = coordinates.y(lat);
|
|
var img = images.pin.get();
|
|
|
|
if(img) {
|
|
var w = options.pin_scale * img.width;
|
|
var h = options.pin_scale * img.height;
|
|
context.drawImage(img, x - w * 0.5, y - h, w, h);
|
|
}
|
|
context.textAlign = 'center';
|
|
context.font = options.font;
|
|
if(options.truncate_labels) {
|
|
label = label.substr(0, 2);
|
|
context.fillStyle = rgba(options.text_color, 1);
|
|
context.fillText(label, x, y - h * 0.6)
|
|
} else {
|
|
var ts = getTextSize(label);
|
|
context.fillStyle = rgba(options.background_color, 1);
|
|
var tl = x - 0.5 * ts.width;
|
|
if(tl < 0) {
|
|
x -= tl;
|
|
tl = 0;
|
|
} else if(tl + ts.width > options.width) {
|
|
x = options.width - ts.width * 0.5;
|
|
tl = options.width - ts.width;
|
|
}
|
|
|
|
context.fillRect(
|
|
tl,
|
|
y + 4,
|
|
ts.width,
|
|
ts.height
|
|
);
|
|
context.fillStyle = rgba(options.text_color, 1);
|
|
context.textBaseline = 'middle';
|
|
context.fillText(label, x, y + 4 + ts.height * 0.5);
|
|
}
|
|
}
|
|
|
|
|
|
this.addMistake = function(lng, lat) {
|
|
var x = coordinates.x(lng);
|
|
var y = coordinates.y(lat);
|
|
context.strokeStyle = '#FF0000';
|
|
context.lineWidth = options.line_width;
|
|
context.beginPath();
|
|
context.arc(x, y, 20, 20, 0, 2 * Math.PI);
|
|
context.stroke();
|
|
}
|
|
|
|
|
|
// init
|
|
var images = {
|
|
map: new ImageLoader(options.map_file, this.clear.bind(this)),
|
|
pin: new ImageLoader(options.pin_file)
|
|
}
|
|
var canvas = document.createElement('canvas');
|
|
canvas.width = options.width;
|
|
canvas.height = options.height;
|
|
if(options.display) {
|
|
options.parent.appendChild(canvas);
|
|
}
|
|
var context = canvas.getContext('2d');
|
|
var coordinates = new CoordinatesConverter();
|
|
|
|
this.destroy = function() {
|
|
options.parent.removeChild(canvas);
|
|
canvas = null;
|
|
}
|
|
} |