kopia lustrzana https://gitlab.com/gridtracker.org/gridtracker
				
				
				
			
		
			
				
	
	
		
			353 wiersze
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			353 wiersze
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
| "use strict";
 | |
| 
 | |
| var D2R = Math.PI / 180;
 | |
| var R2D = 180 / Math.PI;
 | |
| 
 | |
| var Coord = function (lon, lat)
 | |
| {
 | |
|   this.lon = lon;
 | |
|   this.lat = lat;
 | |
|   this.x = D2R * lon;
 | |
|   this.y = D2R * lat;
 | |
| };
 | |
| 
 | |
| Coord.prototype.view = function ()
 | |
| {
 | |
|   return String(this.lon).slice(0, 4) + "," + String(this.lat).slice(0, 4);
 | |
| };
 | |
| 
 | |
| Coord.prototype.antipode = function ()
 | |
| {
 | |
|   var anti_lat = -1 * this.lat;
 | |
|   var anti_lon = this.lon < 0 ? 180 + this.lon : (180 - this.lon) * -1;
 | |
|   return new Coord(anti_lon, anti_lat);
 | |
| };
 | |
| 
 | |
| var LineString = function ()
 | |
| {
 | |
|   this.coords = [];
 | |
|   this.length = 0;
 | |
| };
 | |
| 
 | |
| LineString.prototype.move_to = function (coord)
 | |
| {
 | |
|   this.length++;
 | |
|   this.coords.push(coord);
 | |
| };
 | |
| 
 | |
| var Arc = function (properties)
 | |
| {
 | |
|   this.properties = properties || {};
 | |
|   this.geometries = [];
 | |
| };
 | |
| 
 | |
| Arc.prototype.json = function ()
 | |
| {
 | |
|   if (this.geometries.length <= 0)
 | |
|   {
 | |
|     return {
 | |
|       geometry: { type: "LineString", coordinates: null },
 | |
|       type: "Feature",
 | |
|       properties: this.properties
 | |
|     };
 | |
|   }
 | |
|   else if (this.geometries.length == 1)
 | |
|   {
 | |
|     return {
 | |
|       geometry: { type: "LineString", coordinates: this.geometries[0].coords },
 | |
|       type: "Feature",
 | |
|       properties: this.properties
 | |
|     };
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     var multiline = [];
 | |
|     for (var i = 0; i < this.geometries.length; i++)
 | |
|     {
 | |
|       multiline.push(this.geometries[i].coords);
 | |
|     }
 | |
|     return {
 | |
|       geometry: { type: "MultiLineString", coordinates: multiline },
 | |
|       type: "Feature",
 | |
|       properties: this.properties
 | |
|     };
 | |
|   }
 | |
| };
 | |
| 
 | |
| // TODO - output proper multilinestring
 | |
| Arc.prototype.wkt = function ()
 | |
| {
 | |
|   var wkt_string = "";
 | |
|   var wkt = "LINESTRING(";
 | |
|   var collect = function (c)
 | |
|   {
 | |
|     wkt += c[0] + " " + c[1] + ",";
 | |
|   };
 | |
|   for (var i = 0; i < this.geometries.length; i++)
 | |
|   {
 | |
|     if (this.geometries[i].coords.length === 0)
 | |
|     {
 | |
|       return "LINESTRING(empty)";
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       var coords = this.geometries[i].coords;
 | |
|       coords.forEach(collect);
 | |
|       wkt_string += wkt.substring(0, wkt.length - 1) + ")";
 | |
|     }
 | |
|   }
 | |
|   return wkt_string;
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * http://en.wikipedia.org/wiki/Great-circle_distance
 | |
|  *
 | |
|  */
 | |
| var GreatCircle = function (start, end, properties)
 | |
| {
 | |
|   if (!start || start.x === undefined || start.y === undefined)
 | |
|   {
 | |
|     throw new Error(
 | |
|       "GreatCircle constructor expects two args: start and end objects with x and y properties"
 | |
|     );
 | |
|   }
 | |
|   if (!end || end.x === undefined || end.y === undefined)
 | |
|   {
 | |
|     throw new Error(
 | |
|       "GreatCircle constructor expects two args: start and end objects with x and y properties"
 | |
|     );
 | |
|   }
 | |
|   this.start = new Coord(start.x, start.y);
 | |
|   this.end = new Coord(end.x, end.y);
 | |
|   this.properties = properties || {};
 | |
| 
 | |
|   var w = this.start.x - this.end.x;
 | |
|   var h = this.start.y - this.end.y;
 | |
|   var z =
 | |
|     Math.pow(Math.sin(h / 2.0), 2) +
 | |
|     Math.cos(this.start.y) *
 | |
|       Math.cos(this.end.y) *
 | |
|       Math.pow(Math.sin(w / 2.0), 2);
 | |
|   this.g = 2.0 * Math.asin(Math.sqrt(z));
 | |
| 
 | |
|   if (this.g == Math.PI)
 | |
|   {
 | |
|     throw new Error(
 | |
|       "it appears " +
 | |
|         start.view() +
 | |
|         " and " +
 | |
|         end.view() +
 | |
|         " are 'antipodal', e.g diametrically opposite, thus there is no single route but rather infinite"
 | |
|     );
 | |
|   }
 | |
|   else if (isNaN(this.g))
 | |
|   {
 | |
|     throw new Error(
 | |
|       "could not calculate great circle between " + start + " and " + end
 | |
|     );
 | |
|   }
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * http://williams.best.vwh.net/avform.htm#Intermediate
 | |
|  */
 | |
| GreatCircle.prototype.interpolate = function (f)
 | |
| {
 | |
|   var A = Math.sin((1 - f) * this.g) / Math.sin(this.g);
 | |
|   var B = Math.sin(f * this.g) / Math.sin(this.g);
 | |
|   var x =
 | |
|     A * Math.cos(this.start.y) * Math.cos(this.start.x) +
 | |
|     B * Math.cos(this.end.y) * Math.cos(this.end.x);
 | |
|   var y =
 | |
|     A * Math.cos(this.start.y) * Math.sin(this.start.x) +
 | |
|     B * Math.cos(this.end.y) * Math.sin(this.end.x);
 | |
|   var z = A * Math.sin(this.start.y) + B * Math.sin(this.end.y);
 | |
|   var lat = R2D * Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
 | |
|   var lon = R2D * Math.atan2(y, x);
 | |
|   return [lon, lat];
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Generate points along the great circle
 | |
|  */
 | |
| GreatCircle.prototype.Arc = function (npoints, options)
 | |
| {
 | |
|   var first_pass = [];
 | |
|   if (!npoints || npoints <= 2)
 | |
|   {
 | |
|     first_pass.push([this.start.lon, this.start.lat]);
 | |
|     first_pass.push([this.end.lon, this.end.lat]);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     var delta = 1.0 / (npoints - 1);
 | |
|     for (var i = 0; i < npoints; ++i)
 | |
|     {
 | |
|       var step = delta * i;
 | |
|       var pair = this.interpolate(step);
 | |
|       first_pass.push(pair);
 | |
|     }
 | |
|   }
 | |
|   /* partial port of dateline handling from:
 | |
|       gdal/ogr/ogrgeometryfactory.cpp
 | |
| 
 | |
|       TODO - does not handle all wrapping scenarios yet
 | |
|     */
 | |
|   var bHasBigDiff = false;
 | |
|   var dfMaxSmallDiffLong = 0;
 | |
|   // from http://www.gdal.org/ogr2ogr.html
 | |
|   // -datelineoffset:
 | |
|   // (starting with GDAL 1.10) offset from dateline in degrees (default long. = +/- 10deg, geometries within 170deg to -170deg will be splited)
 | |
|   var dfDateLineOffset = options && options.offset ? options.offset : 10;
 | |
|   var dfLeftBorderX = 180 - dfDateLineOffset;
 | |
|   var dfRightBorderX = -180 + dfDateLineOffset;
 | |
|   var dfDiffSpace = 360 - dfDateLineOffset;
 | |
| 
 | |
|   // https://github.com/OSGeo/gdal/blob/7bfb9c452a59aac958bff0c8386b891edf8154ca/gdal/ogr/ogrgeometryfactory.cpp#L2342
 | |
|   for (var j = 1; j < first_pass.length; ++j)
 | |
|   {
 | |
|     var dfPrevX = first_pass[j - 1][0];
 | |
|     var dfX = first_pass[j][0];
 | |
|     var dfDiffLong = Math.abs(dfX - dfPrevX);
 | |
|     if (
 | |
|       dfDiffLong > dfDiffSpace &&
 | |
|       ((dfX > dfLeftBorderX && dfPrevX < dfRightBorderX) ||
 | |
|         (dfPrevX > dfLeftBorderX && dfX < dfRightBorderX))
 | |
|     )
 | |
|     {
 | |
|       bHasBigDiff = true;
 | |
|     }
 | |
|     else if (dfDiffLong > dfMaxSmallDiffLong)
 | |
|     {
 | |
|       dfMaxSmallDiffLong = dfDiffLong;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var poMulti = [];
 | |
|   if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
 | |
|   {
 | |
|     var poNewLS = [];
 | |
|     poMulti.push(poNewLS);
 | |
|     for (var k = 0; k < first_pass.length; ++k)
 | |
|     {
 | |
|       var dfX0 = parseFloat(first_pass[k][0]);
 | |
|       if (k > 0 && Math.abs(dfX0 - first_pass[k - 1][0]) > dfDiffSpace)
 | |
|       {
 | |
|         var dfX1 = parseFloat(first_pass[k - 1][0]);
 | |
|         var dfY1 = parseFloat(first_pass[k - 1][1]);
 | |
|         var dfX2 = parseFloat(first_pass[k][0]);
 | |
|         var dfY2 = parseFloat(first_pass[k][1]);
 | |
|         if (
 | |
|           dfX1 > -180 &&
 | |
|           dfX1 < dfRightBorderX &&
 | |
|           dfX2 == 180 &&
 | |
|           k + 1 < first_pass.length &&
 | |
|           first_pass[k - 1][0] > -180 &&
 | |
|           first_pass[k - 1][0] < dfRightBorderX
 | |
|         )
 | |
|         {
 | |
|           poNewLS.push([-180, first_pass[k][1]]);
 | |
|           k++;
 | |
|           poNewLS.push([first_pass[k][0], first_pass[k][1]]);
 | |
|           continue;
 | |
|         }
 | |
|         else if (
 | |
|           dfX1 > dfLeftBorderX &&
 | |
|           dfX1 < 180 &&
 | |
|           dfX2 == -180 &&
 | |
|           k + 1 < first_pass.length &&
 | |
|           first_pass[k - 1][0] > dfLeftBorderX &&
 | |
|           first_pass[k - 1][0] < 180
 | |
|         )
 | |
|         {
 | |
|           poNewLS.push([180, first_pass[k][1]]);
 | |
|           k++;
 | |
|           poNewLS.push([first_pass[k][0], first_pass[k][1]]);
 | |
|           continue;
 | |
|         }
 | |
| 
 | |
|         if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
 | |
|         {
 | |
|           // swap dfX1, dfX2
 | |
|           var tmpX = dfX1;
 | |
|           dfX1 = dfX2;
 | |
|           dfX2 = tmpX;
 | |
|           // swap dfY1, dfY2
 | |
|           var tmpY = dfY1;
 | |
|           dfY1 = dfY2;
 | |
|           dfY2 = tmpY;
 | |
|         }
 | |
|         if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
 | |
|         {
 | |
|           dfX2 += 360;
 | |
|         }
 | |
| 
 | |
|         if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
 | |
|         {
 | |
|           var dfRatio = (180 - dfX1) / (dfX2 - dfX1);
 | |
|           var dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
 | |
|           poNewLS.push([
 | |
|             first_pass[k - 1][0] > dfLeftBorderX ? 180 : -180,
 | |
|             dfY
 | |
|           ]);
 | |
|           poNewLS = [];
 | |
|           poNewLS.push([
 | |
|             first_pass[k - 1][0] > dfLeftBorderX ? -180 : 180,
 | |
|             dfY
 | |
|           ]);
 | |
|           poMulti.push(poNewLS);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           poNewLS = [];
 | |
|           poMulti.push(poNewLS);
 | |
|         }
 | |
|         poNewLS.push([dfX0, first_pass[k][1]]);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         poNewLS.push([first_pass[k][0], first_pass[k][1]]);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     // add normally
 | |
|     var poNewLS0 = [];
 | |
|     poMulti.push(poNewLS0);
 | |
|     for (var l = 0; l < first_pass.length; ++l)
 | |
|     {
 | |
|       poNewLS0.push([first_pass[l][0], first_pass[l][1]]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var arc = new Arc(this.properties);
 | |
|   for (var m = 0; m < poMulti.length; ++m)
 | |
|   {
 | |
|     var line = new LineString();
 | |
|     arc.geometries.push(line);
 | |
|     var points = poMulti[m];
 | |
|     for (var j0 = 0; j0 < points.length; ++j0)
 | |
|     {
 | |
|       line.move_to(points[j0]);
 | |
|     }
 | |
|   }
 | |
|   return arc;
 | |
| };
 | |
| 
 | |
| if (typeof module !== "undefined" && typeof module.exports !== "undefined")
 | |
| {
 | |
|   // nodejs
 | |
|   module.exports.Coord = Coord;
 | |
|   module.exports.Arc = Arc;
 | |
|   module.exports.GreatCircle = GreatCircle;
 | |
| }
 | |
| else
 | |
| {
 | |
|   // browser
 | |
|   var arc = {};
 | |
|   arc.Coord = Coord;
 | |
|   arc.Arc = Arc;
 | |
|   arc.GreatCircle = GreatCircle;
 | |
| }
 |