Carga
This commit is contained in:
2025-04-17 00:35:33 -06:00
parent 4977462629
commit 67fc72aed5
1333 changed files with 1077639 additions and 0 deletions

View File

@@ -0,0 +1,530 @@
/* *
*
* (c) 2021 Torstein Honsi
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
import PC from '../Core/Geometry/PolygonClip.js';
var clipLineString = PC.clipLineString, clipPolygon = PC.clipPolygon;
import registry from './Projections/ProjectionRegistry.js';
import U from '../Core/Utilities.js';
var clamp = U.clamp, erase = U.erase;
var deg2rad = Math.PI * 2 / 360;
// Safe padding on either side of the antimeridian to avoid points being
// projected to the wrong side of the plane
var floatCorrection = 0.000001;
// Keep longitude within -180 and 180. This is faster than using the modulo
// operator, and preserves the distinction between -180 and 180.
var wrapLon = function (lon) {
// Replacing the if's with while would increase the range, but make it prone
// to crashes on bad data
if (lon < -180) {
lon += 360;
}
if (lon > 180) {
lon -= 360;
}
return lon;
};
var Projection = /** @class */ (function () {
function Projection(options) {
if (options === void 0) { options = {}; }
// Whether the chart has points, lines or polygons given as coordinates
// with positive up, as opposed to paths in the SVG plane with positive
// down.
this.hasCoordinates = false;
// Whether the chart has true projection as opposed to pre-projected geojson
// as in the legacy map collection.
this.hasGeoProjection = false;
this.maxLatitude = 90;
this.options = options;
var name = options.name, projectedBounds = options.projectedBounds, rotation = options.rotation;
this.rotator = rotation ? this.getRotator(rotation) : void 0;
var ProjectionDefinition = name ? Projection.registry[name] : void 0;
if (ProjectionDefinition) {
this.def = new ProjectionDefinition(options);
}
var _a = this, def = _a.def, rotator = _a.rotator;
if (def) {
this.maxLatitude = def.maxLatitude || 90;
this.hasGeoProjection = true;
}
if (rotator && def) {
this.forward = function (lonLat) {
return def.forward(rotator.forward(lonLat));
};
this.inverse = function (xy) {
return rotator.inverse(def.inverse(xy));
};
}
else if (def) {
this.forward = function (lonLat) { return def.forward(lonLat); };
this.inverse = function (xy) { return def.inverse(xy); };
}
else if (rotator) {
this.forward = rotator.forward;
this.inverse = rotator.inverse;
}
// Projected bounds/clipping
this.bounds = projectedBounds === 'world' ?
def && def.bounds :
projectedBounds;
}
// Add a projection definition to the registry, accessible by its `name`.
Projection.add = function (name, definition) {
Projection.registry[name] = definition;
};
// Calculate the great circle between two given coordinates
Projection.greatCircle = function (point1, point2, inclusive) {
var atan2 = Math.atan2, cos = Math.cos, sin = Math.sin, sqrt = Math.sqrt;
var lat1 = point1[1] * deg2rad;
var lon1 = point1[0] * deg2rad;
var lat2 = point2[1] * deg2rad;
var lon2 = point2[0] * deg2rad;
var deltaLat = lat2 - lat1;
var deltaLng = lon2 - lon1;
var calcA = sin(deltaLat / 2) * sin(deltaLat / 2) +
cos(lat1) * cos(lat2) * sin(deltaLng / 2) * sin(deltaLng / 2);
var calcB = 2 * atan2(sqrt(calcA), sqrt(1 - calcA));
var distance = calcB * 6371e3; // in meters
var jumps = Math.round(distance / 500000); // 500 km each jump
var lineString = [];
if (inclusive) {
lineString.push(point1);
}
if (jumps > 1) {
var step = 1 / jumps;
for (var fraction = step; fraction < 0.999; // Account for float errors
fraction += step) {
var A = sin((1 - fraction) * calcB) / sin(calcB);
var B = sin(fraction * calcB) / sin(calcB);
var x = A * cos(lat1) * cos(lon1) + B * cos(lat2) * cos(lon2);
var y = A * cos(lat1) * sin(lon1) + B * cos(lat2) * sin(lon2);
var z = A * sin(lat1) + B * sin(lat2);
var lat3 = atan2(z, sqrt(x * x + y * y));
var lon3 = atan2(y, x);
lineString.push([lon3 / deg2rad, lat3 / deg2rad]);
}
}
if (inclusive) {
lineString.push(point2);
}
return lineString;
};
Projection.insertGreatCircles = function (poly) {
var i = poly.length - 1;
while (i--) {
// Distance in degrees, either in lon or lat. Avoid heavy
// calculation of true distance.
var roughDistance = Math.max(Math.abs(poly[i][0] - poly[i + 1][0]), Math.abs(poly[i][1] - poly[i + 1][1]));
if (roughDistance > 10) {
var greatCircle = Projection.greatCircle(poly[i], poly[i + 1]);
if (greatCircle.length) {
poly.splice.apply(poly, __spreadArray([i + 1, 0], greatCircle, false));
}
}
}
};
Projection.toString = function (options) {
var _a = options || {}, name = _a.name, rotation = _a.rotation;
return [name, rotation && rotation.join(',')].join(';');
};
Projection.prototype.lineIntersectsBounds = function (line) {
var _a = this.bounds || {}, x1 = _a.x1, x2 = _a.x2, y1 = _a.y1, y2 = _a.y2;
var getIntersect = function (line, dim, val) {
var p1 = line[0], p2 = line[1], otherDim = dim ? 0 : 1;
// Check if points are on either side of the line
if (typeof val === 'number' && p1[dim] >= val !== p2[dim] >= val) {
var fraction = ((val - p1[dim]) / (p2[dim] - p1[dim])), crossingVal = p1[otherDim] +
fraction * (p2[otherDim] - p1[otherDim]);
return dim ? [crossingVal, val] : [val, crossingVal];
}
};
var intersection, ret = line[0];
if ((intersection = getIntersect(line, 0, x1))) {
ret = intersection;
// Assuming line[1] was originally outside, replace it with the
// intersection point so that the horizontal intersection will
// be correct.
line[1] = intersection;
}
else if ((intersection = getIntersect(line, 0, x2))) {
ret = intersection;
line[1] = intersection;
}
if ((intersection = getIntersect(line, 1, y1))) {
ret = intersection;
}
else if ((intersection = getIntersect(line, 1, y2))) {
ret = intersection;
}
return ret;
};
/*
* Take the rotation options and return the appropriate projection functions
*/
Projection.prototype.getRotator = function (rotation) {
var deltaLambda = rotation[0] * deg2rad, deltaPhi = (rotation[1] || 0) * deg2rad, deltaGamma = (rotation[2] || 0) * deg2rad;
var cosDeltaPhi = Math.cos(deltaPhi), sinDeltaPhi = Math.sin(deltaPhi), cosDeltaGamma = Math.cos(deltaGamma), sinDeltaGamma = Math.sin(deltaGamma);
if (deltaLambda === 0 && deltaPhi === 0 && deltaGamma === 0) {
// Don't waste processing time
return;
}
return {
forward: function (lonLat) {
// Lambda (lon) rotation
var lon = lonLat[0] * deg2rad + deltaLambda;
// Phi (lat) and gamma rotation
var lat = lonLat[1] * deg2rad, cosLat = Math.cos(lat), x = Math.cos(lon) * cosLat, y = Math.sin(lon) * cosLat, sinLat = Math.sin(lat), k = sinLat * cosDeltaPhi + x * sinDeltaPhi;
return [
Math.atan2(y * cosDeltaGamma - k * sinDeltaGamma, x * cosDeltaPhi - sinLat * sinDeltaPhi) / deg2rad,
Math.asin(k * cosDeltaGamma + y * sinDeltaGamma) / deg2rad
];
},
inverse: function (rLonLat) {
// Lambda (lon) unrotation
var lon = rLonLat[0] * deg2rad;
// Phi (lat) and gamma unrotation
var lat = rLonLat[1] * deg2rad, cosLat = Math.cos(lat), x = Math.cos(lon) * cosLat, y = Math.sin(lon) * cosLat, sinLat = Math.sin(lat), k = sinLat * cosDeltaGamma - y * sinDeltaGamma;
return [
(Math.atan2(y * cosDeltaGamma + sinLat * sinDeltaGamma, x * cosDeltaPhi + k * sinDeltaPhi) - deltaLambda) / deg2rad,
Math.asin(k * cosDeltaPhi - x * sinDeltaPhi) / deg2rad
];
}
};
};
// Project a lonlat coordinate position to xy. Dynamically overridden when
// projection is set.
Projection.prototype.forward = function (lonLat) {
return lonLat;
};
// Unproject an xy chart coordinate position to lonlat. Dynamically
// overridden when projection is set.
Projection.prototype.inverse = function (xy) {
return xy;
};
Projection.prototype.cutOnAntimeridian = function (poly, isPolygon) {
var antimeridian = 180;
var intersections = [];
var polygons = [poly];
poly.forEach(function (lonLat, i) {
var previousLonLat = poly[i - 1];
if (!i) {
if (!isPolygon) {
return;
}
// Else, wrap to beginning
previousLonLat = poly[poly.length - 1];
}
var lon1 = previousLonLat[0], lon2 = lonLat[0];
if (
// Both points, after rotating for antimeridian, are on the far
// side of the Earth
(lon1 < -90 || lon1 > 90) &&
(lon2 < -90 || lon2 > 90) &&
// ... and on either side of the plane
(lon1 > 0) !== (lon2 > 0)) {
// Interpolate to the intersection latitude
var fraction = clamp((antimeridian - (lon1 + 360) % 360) /
((lon2 + 360) % 360 - (lon1 + 360) % 360), 0, 1), lat = (previousLonLat[1] +
fraction * (lonLat[1] - previousLonLat[1]));
intersections.push({
i: i,
lat: lat,
direction: lon1 < 0 ? 1 : -1,
previousLonLat: previousLonLat,
lonLat: lonLat
});
}
});
var polarIntersection;
if (intersections.length) {
if (isPolygon) {
// Simplified use of the even-odd rule, if there is an odd
// amount of intersections between the polygon and the
// antimeridian, the pole is inside the polygon. Applies
// primarily to Antarctica.
if (intersections.length % 2 === 1) {
polarIntersection = intersections.slice().sort(function (a, b) { return Math.abs(b.lat) - Math.abs(a.lat); })[0];
erase(intersections, polarIntersection);
}
// Pull out slices of the polygon that is on the opposite side
// of the antimeridian compared to the starting point
var i = intersections.length - 2;
while (i >= 0) {
var index = intersections[i].i;
var lonPlus = wrapLon(antimeridian +
intersections[i].direction * floatCorrection);
var lonMinus = wrapLon(antimeridian -
intersections[i].direction * floatCorrection);
var slice = poly.splice.apply(poly, __spreadArray([index,
intersections[i + 1].i - index], Projection.greatCircle([lonPlus, intersections[i].lat], [lonPlus, intersections[i + 1].lat], true), false));
// Add interpolated points close to the cut
slice.push.apply(slice, Projection.greatCircle([lonMinus, intersections[i + 1].lat], [lonMinus, intersections[i].lat], true));
polygons.push(slice);
i -= 2;
}
// Insert dummy points close to the pole
if (polarIntersection) {
for (var i_1 = 0; i_1 < polygons.length; i_1++) {
var direction = polarIntersection.direction, lat = polarIntersection.lat, poly_1 = polygons[i_1], indexOf = poly_1.indexOf(polarIntersection.lonLat);
if (indexOf > -1) {
var polarLatitude = (lat < 0 ? -1 : 1) *
this.maxLatitude;
var lon1 = wrapLon(antimeridian +
direction * floatCorrection);
var lon2 = wrapLon(antimeridian -
direction * floatCorrection);
var polarSegment = Projection.greatCircle([lon1, lat], [lon1, polarLatitude], true);
// Circle around the pole point in order to make
// polygon clipping right. Without this, Antarctica
// would wrap the wrong way in an LLC projection
// with parallels [30, 40].
for (var lon = lon1 + 120 * direction; lon > -180 && lon < 180; lon += 120 * direction) {
polarSegment.push([lon, polarLatitude]);
}
polarSegment.push.apply(polarSegment, Projection.greatCircle([lon2, polarLatitude], [lon2, polarIntersection.lat], true));
poly_1.splice.apply(poly_1, __spreadArray([indexOf,
0], polarSegment, false));
break;
}
}
}
// Map lines, not closed
}
else {
var i = intersections.length;
while (i--) {
var index = intersections[i].i;
var slice = poly.splice(index, poly.length,
// Add interpolated point close to the cut
[
wrapLon(antimeridian +
intersections[i].direction * floatCorrection),
intersections[i].lat
]);
// Add interpolated point close to the cut
slice.unshift([
wrapLon(antimeridian -
intersections[i].direction * floatCorrection),
intersections[i].lat
]);
polygons.push(slice);
}
}
}
return polygons;
};
// Take a GeoJSON geometry and return a translated SVGPath
Projection.prototype.path = function (geometry) {
var _this = this;
var _a = this, bounds = _a.bounds, def = _a.def, rotator = _a.rotator;
var antimeridian = 180;
var path = [];
var isPolygon = geometry.type === 'Polygon' ||
geometry.type === 'MultiPolygon';
// @todo: It doesn't really have to do with whether north is
// positive. It depends on whether the coordinates are
// pre-projected.
var hasGeoProjection = this.hasGeoProjection;
// Detect whether we need to do antimeridian cutting and clipping to
// bounds. The alternative (currently for Orthographic) is to apply a
// clip angle.
var projectingToPlane = !def || def.antimeridianCutting !== false;
// We need to rotate in a separate step before applying antimeridian
// cutting
var preclip = projectingToPlane ? rotator : void 0;
var postclip = projectingToPlane ? (def || this) : this;
var boundsPolygon;
if (bounds) {
boundsPolygon = [
[bounds.x1, bounds.y1],
[bounds.x2, bounds.y1],
[bounds.x2, bounds.y2],
[bounds.x1, bounds.y2]
];
}
var addToPath = function (polygon) {
// Create a copy of the original coordinates. The copy applies a
// correction of points close to the antimeridian in order to
// prevent the points to be projected to the wrong side of the
// plane. Float errors in topojson or in the projection may cause
// that.
var poly = polygon.map(function (lonLat) {
if (projectingToPlane) {
if (preclip) {
lonLat = preclip.forward(lonLat);
}
var lon = lonLat[0];
if (Math.abs(lon - antimeridian) < floatCorrection) {
if (lon < antimeridian) {
lon = antimeridian - floatCorrection;
}
else {
lon = antimeridian + floatCorrection;
}
}
lonLat = [lon, lonLat[1]];
}
return lonLat;
});
var polygons = [poly];
if (hasGeoProjection) {
// Insert great circles into long straight lines
Projection.insertGreatCircles(poly);
if (projectingToPlane) {
polygons = _this.cutOnAntimeridian(poly, isPolygon);
}
}
polygons.forEach(function (poly) {
if (poly.length < 2) {
return;
}
var movedTo = false;
var firstValidLonLat;
var lastValidLonLat;
var gap = false;
var pushToPath = function (point) {
if (!movedTo) {
path.push(['M', point[0], point[1]]);
movedTo = true;
}
else {
path.push(['L', point[0], point[1]]);
}
};
var someOutside = false, someInside = false;
var points = poly.map(function (lonLat) {
var xy = postclip.forward(lonLat);
if (xy.outside) {
someOutside = true;
}
else {
someInside = true;
}
// Mercator projects pole points to Infinity, and
// clipPolygon is not able to handle it.
if (xy[1] === Infinity) {
xy[1] = 10e9;
}
else if (xy[1] === -Infinity) {
xy[1] = -10e9;
}
return xy;
});
if (projectingToPlane) {
// Wrap around in order for pointInPolygon to work
if (isPolygon) {
points.push(points[0]);
}
if (someOutside) {
// All points are outside
if (!someInside) {
return;
}
// Some inside, some outside. Clip to the bounds.
if (boundsPolygon) {
// Polygons
if (isPolygon) {
points = clipPolygon(points, boundsPolygon);
// Linestrings
}
else if (bounds) {
clipLineString(points, boundsPolygon)
.forEach(function (points) {
movedTo = false;
points.forEach(pushToPath);
});
return;
}
}
}
points.forEach(pushToPath);
// For orthographic projection, or when a clipAngle applies
}
else {
for (var i = 0; i < points.length; i++) {
var lonLat = poly[i], point = points[i];
if (!point.outside) {
// In order to be able to interpolate if the first
// or last point is invalid (on the far side of the
// globe in an orthographic projection), we need to
// push the first valid point to the end of the
// polygon.
if (isPolygon && !firstValidLonLat) {
firstValidLonLat = lonLat;
poly.push(lonLat);
points.push(point);
}
// When entering the first valid point after a gap
// of invalid points, typically on the far side of
// the globe in an orthographic projection.
if (gap && lastValidLonLat) {
// For areas, in an orthographic projection, the
// great circle between two visible points will
// be close to the horizon. A possible exception
// may be when the two points are on opposite
// sides of the globe. It that poses a problem,
// we may have to rewrite this to use the small
// circle related to the current lon0 and lat0.
if (isPolygon && hasGeoProjection) {
var greatCircle = Projection.greatCircle(lastValidLonLat, lonLat);
greatCircle.forEach(function (lonLat) {
return pushToPath(postclip.forward(lonLat));
});
// For lines, just jump over the gap
}
else {
movedTo = false;
}
}
pushToPath(point);
lastValidLonLat = lonLat;
gap = false;
}
else {
gap = true;
}
}
}
});
};
if (geometry.type === 'LineString') {
addToPath(geometry.coordinates);
}
else if (geometry.type === 'MultiLineString') {
geometry.coordinates.forEach(function (c) { return addToPath(c); });
}
else if (geometry.type === 'Polygon') {
geometry.coordinates.forEach(function (c) { return addToPath(c); });
if (path.length) {
path.push(['Z']);
}
}
else if (geometry.type === 'MultiPolygon') {
geometry.coordinates.forEach(function (polygons) {
polygons.forEach(function (c) { return addToPath(c); });
});
if (path.length) {
path.push(['Z']);
}
}
return path;
};
Projection.registry = registry;
return Projection;
}());
export default Projection;