445 lines
13 KiB
JavaScript
445 lines
13 KiB
JavaScript
/* Copyright (c) 2016 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (French BSD license)
|
|
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
|
|
|
|
Usefull function to handle geometric operations
|
|
*/
|
|
|
|
import ol_geom_LineString from 'ol/geom/LineString.js'
|
|
import ol_geom_LinearRing from 'ol/geom/LinearRing.js'
|
|
import ol_geom_MultiLineString from 'ol/geom/MultiLineString.js'
|
|
import ol_geom_MultiPoint from 'ol/geom/MultiPoint.js'
|
|
import ol_geom_MultiPolygon from 'ol/geom/MultiPolygon.js'
|
|
import ol_geom_Point from 'ol/geom/Point.js'
|
|
import ol_geom_Polygon from 'ol/geom/Polygon.js'
|
|
import ol_geom_Circle from 'ol/geom/Circle.js'
|
|
import {getCenter as ol_extent_getCenter} from 'ol/extent.js'
|
|
import {buffer as ol_extent_buffer} from 'ol/extent.js'
|
|
|
|
/** Distance beetween 2 points
|
|
* Usefull geometric functions
|
|
* @param {ol.Coordinate} p1 first point
|
|
* @param {ol.Coordinate} p2 second point
|
|
* @return {number} distance
|
|
*/
|
|
var ol_coordinate_dist2d = function(p1, p2) {
|
|
var dx = p1[0]-p2[0];
|
|
var dy = p1[1]-p2[1];
|
|
return Math.sqrt(dx*dx+dy*dy);
|
|
}
|
|
|
|
/** 2 points are equal
|
|
* Usefull geometric functions
|
|
* @param {ol.Coordinate} p1 first point
|
|
* @param {ol.Coordinate} p2 second point
|
|
* @return {boolean}
|
|
*/
|
|
var ol_coordinate_equal = function(p1, p2) {
|
|
return (p1[0]==p2[0] && p1[1]==p2[1]);
|
|
}
|
|
|
|
/** Get center coordinate of a feature
|
|
* @param {ol.Feature} f
|
|
* @return {ol.coordinate} the center
|
|
*/
|
|
var ol_coordinate_getFeatureCenter = function(f) {
|
|
return ol_coordinate_getGeomCenter (f.getGeometry());
|
|
};
|
|
|
|
/** Get center coordinate of a geometry
|
|
* @param {ol.geom.Geometry} geom
|
|
* @return {ol.Coordinate} the center
|
|
*/
|
|
var ol_coordinate_getGeomCenter = function(geom) {
|
|
switch (geom.getType()) {
|
|
case 'Point':
|
|
return geom.getCoordinates();
|
|
case "MultiPolygon":
|
|
geom = geom.getPolygon(0);
|
|
// fallthrough
|
|
case "Polygon":
|
|
return geom.getInteriorPoint().getCoordinates();
|
|
default:
|
|
return geom.getClosestPoint(ol_extent_getCenter(geom.getExtent()));
|
|
}
|
|
};
|
|
|
|
/** Offset a polyline
|
|
* @param {Array<ol.Coordinate>} coords
|
|
* @param {number} offset
|
|
* @return {Array<ol.Coordinate>} resulting coord
|
|
* @see http://stackoverflow.com/a/11970006/796832
|
|
* @see https://drive.google.com/viewerng/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxqa2dhZGdldHN0b3JlfGd4OjQ4MzI5M2Y0MjNmNzI2MjY
|
|
*/
|
|
var ol_coordinate_offsetCoords = function (coords, offset) {
|
|
var path = [];
|
|
var N = coords.length-1;
|
|
var max = N;
|
|
var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1;
|
|
var p0, p1, p2;
|
|
var isClosed = ol_coordinate_equal(coords[0],coords[N]);
|
|
if (!isClosed) {
|
|
p0 = coords[0];
|
|
p1 = coords[1];
|
|
p2 = [
|
|
p0[0] + (p1[1] - p0[1]) / ol_coordinate_dist2d(p0,p1) *offset,
|
|
p0[1] - (p1[0] - p0[0]) / ol_coordinate_dist2d(p0,p1) *offset
|
|
];
|
|
path.push(p2);
|
|
coords.push(coords[N])
|
|
N++;
|
|
max--;
|
|
}
|
|
for (var i = 0; i < max; i++) {
|
|
p0 = coords[i];
|
|
p1 = coords[(i+1) % N];
|
|
p2 = coords[(i+2) % N];
|
|
|
|
mi = (p1[1] - p0[1])/(p1[0] - p0[0]);
|
|
mi1 = (p2[1] - p1[1])/(p2[0] - p1[0]);
|
|
// Prevent alignements
|
|
if (Math.abs(mi-mi1) > 1e-10) {
|
|
li = Math.sqrt((p1[0] - p0[0])*(p1[0] - p0[0])+(p1[1] - p0[1])*(p1[1] - p0[1]));
|
|
li1 = Math.sqrt((p2[0] - p1[0])*(p2[0] - p1[0])+(p2[1] - p1[1])*(p2[1] - p1[1]));
|
|
ri = p0[0] + offset*(p1[1] - p0[1])/li;
|
|
ri1 = p1[0] + offset*(p2[1] - p1[1])/li1;
|
|
si = p0[1] - offset*(p1[0] - p0[0])/li;
|
|
si1 = p1[1] - offset*(p2[0] - p1[0])/li1;
|
|
Xi1 = (mi1*ri1-mi*ri+si-si1) / (mi1-mi);
|
|
Yi1 = (mi*mi1*(ri1-ri)+mi1*si-mi*si1) / (mi1-mi);
|
|
|
|
// Correction for vertical lines
|
|
if(p1[0] - p0[0] == 0) {
|
|
Xi1 = p1[0] + offset*(p1[1] - p0[1])/Math.abs(p1[1] - p0[1]);
|
|
Yi1 = mi1*Xi1 - mi1*ri1 + si1;
|
|
}
|
|
if (p2[0] - p1[0] == 0 ) {
|
|
Xi1 = p2[0] + offset*(p2[1] - p1[1])/Math.abs(p2[1] - p1[1]);
|
|
Yi1 = mi*Xi1 - mi*ri + si;
|
|
}
|
|
|
|
path.push([Xi1, Yi1]);
|
|
}
|
|
}
|
|
if (isClosed) {
|
|
path.push(path[0]);
|
|
} else {
|
|
coords.pop();
|
|
p0 = coords[coords.length-1];
|
|
p1 = coords[coords.length-2];
|
|
p2 = [
|
|
p0[0] - (p1[1] - p0[1]) / ol_coordinate_dist2d(p0,p1) *offset,
|
|
p0[1] + (p1[0] - p0[0]) / ol_coordinate_dist2d(p0,p1) *offset
|
|
];
|
|
path.push(p2);
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/** Find the segment a point belongs to
|
|
* @param {ol.Coordinate} pt
|
|
* @param {Array<ol.Coordinate>} coords
|
|
* @return {} the index (-1 if not found) and the segment
|
|
*/
|
|
var ol_coordinate_findSegment = function (pt, coords) {
|
|
for (var i=0; i<coords.length-1; i++) {
|
|
var p0 = coords[i];
|
|
var p1 = coords[i+1];
|
|
if (ol_coordinate_equal(pt, p0) || ol_coordinate_equal(pt, p1)) {
|
|
return { index:1, segment: [p0,p1] };
|
|
} else {
|
|
var d0 = ol_coordinate_dist2d(p0,p1);
|
|
var v0 = [ (p1[0] - p0[0]) / d0, (p1[1] - p0[1]) / d0 ];
|
|
var d1 = ol_coordinate_dist2d(p0,pt);
|
|
var v1 = [ (pt[0] - p0[0]) / d1, (pt[1] - p0[1]) / d1 ];
|
|
if (Math.abs(v0[0]*v1[1] - v0[1]*v1[0]) < 1e-10) {
|
|
return { index:1, segment: [p0,p1] };
|
|
}
|
|
}
|
|
}
|
|
return { index: -1 };
|
|
};
|
|
|
|
/**
|
|
* Split a Polygon geom with horizontal lines
|
|
* @param {Array<ol.Coordinate>} geom
|
|
* @param {number} y the y to split
|
|
* @param {number} n contour index
|
|
* @return {Array<Array<ol.Coordinate>>}
|
|
*/
|
|
var ol_coordinate_splitH = function (geom, y, n) {
|
|
var x, abs;
|
|
var list = [];
|
|
for (var i=0; i<geom.length-1; i++) {
|
|
// Hole separator?
|
|
if (!geom[i].length || !geom[i+1].length) continue;
|
|
// Intersect
|
|
if (geom[i][1]<=y && geom[i+1][1]>y || geom[i][1]>=y && geom[i+1][1]<y) {
|
|
abs = (y-geom[i][1]) / (geom[i+1][1]-geom[i][1]);
|
|
x = abs * (geom[i+1][0]-geom[i][0]) + geom[i][0];
|
|
list.push ({ contour: n, index: i, pt: [x,y], abs: abs });
|
|
}
|
|
}
|
|
// Sort x
|
|
list.sort(function(a,b) { return a.pt[0] - b.pt[0] });
|
|
// Horizontal segment
|
|
var result = [];
|
|
for (var j=0; j<list.length-1; j += 2) {
|
|
result.push([list[j], list[j+1]])
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/** Create a geometry given a type and coordinates */
|
|
var ol_geom_createFromType = function (type, coordinates) {
|
|
switch (type) {
|
|
case 'LineString': return new ol_geom_LineString(coordinates);
|
|
case 'LinearRing': return new ol_geom_LinearRing(coordinates);
|
|
case 'MultiLineString': return new ol_geom_MultiLineString(coordinates);
|
|
case 'MultiPoint': return new ol_geom_MultiPoint(coordinates);
|
|
case 'MultiPolygon': return new ol_geom_MultiPolygon(coordinates);
|
|
case 'Point': return new ol_geom_Point(coordinates);
|
|
case 'Polygon': return new ol_geom_Polygon(coordinates);
|
|
default:
|
|
console.error('[createFromType] Unsupported type: '+type);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export {ol_geom_createFromType}
|
|
export {ol_coordinate_dist2d, ol_coordinate_equal, ol_coordinate_findSegment, ol_coordinate_getFeatureCenter, ol_coordinate_getGeomCenter, ol_coordinate_offsetCoords, ol_coordinate_splitH}
|
|
|
|
/** Intersect 2 lines
|
|
* @param {Arrar<ol.coordinate>} d1
|
|
* @param {Arrar<ol.coordinate>} d2
|
|
*/
|
|
var ol_coordinate_getIntersectionPoint = function (d1, d2) {
|
|
var d1x = d1[1][0] - d1[0][0];
|
|
var d1y = d1[1][1] - d1[0][1];
|
|
var d2x = d2[1][0] - d2[0][0];
|
|
var d2y = d2[1][1] - d2[0][1];
|
|
var det = d1x * d2y - d1y * d2x;
|
|
if (det != 0) {
|
|
var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / det;
|
|
return [d2[0][0] + k*d2x, d2[0][1] + k*d2y];
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
export { ol_coordinate_getIntersectionPoint }
|
|
|
|
var ol_extent_intersection;
|
|
|
|
(function() {
|
|
// Split at x
|
|
function splitX(pts, x) {
|
|
var pt;
|
|
for (let i=pts.length-1; i>0; i--) {
|
|
if ((pts[i][0]>x && pts[i-1][0]<x) || (pts[i][0]<x && pts[i-1][0]>x)) {
|
|
pt = [ x, (x - pts[i][0]) / (pts[i-1][0]-pts[i][0]) * (pts[i-1][1]-pts[i][1]) + pts[i][1]];
|
|
pts.splice(i, 0, pt);
|
|
}
|
|
}
|
|
}
|
|
// Split at y
|
|
function splitY(pts, y) {
|
|
var pt;
|
|
for (let i=pts.length-1; i>0; i--) {
|
|
if ((pts[i][1]>y && pts[i-1][1]<y) || (pts[i][1]<y && pts[i-1][1]>y)) {
|
|
pt = [ (y - pts[i][1]) / (pts[i-1][1]-pts[i][1]) * (pts[i-1][0]-pts[i][0]) + pts[i][0], y];
|
|
pts.splice(i, 0, pt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Fast polygon intersection with an extent (used for area calculation)
|
|
* @param {ol_extent_Extent} extent
|
|
* @param {ol_geom_Polygon|ol_geom_MultiPolygon} polygon
|
|
* @returns {ol_geom_Polygon|ol_geom_MultiPolygon|null} return null if not a polygon geometry
|
|
*/
|
|
ol_extent_intersection = function(extent, polygon) {
|
|
var poly = (polygon.getType() === 'Polygon');
|
|
if (!poly && polygon.getType() !== 'MultiPolygon') return null;
|
|
var geom = polygon.getCoordinates();
|
|
if (poly) geom = [geom];
|
|
geom.forEach(function(g) {
|
|
g.forEach(function(c) {
|
|
splitX(c, extent[0]);
|
|
splitX(c, extent[2]);
|
|
splitY(c, extent[1]);
|
|
splitY(c, extent[3]);
|
|
});
|
|
})
|
|
// Snap geom to the extent
|
|
geom.forEach(function(g) {
|
|
g.forEach(function(c) {
|
|
c.forEach(function(p) {
|
|
if (p[0]<extent[0]) p[0] = extent[0];
|
|
else if (p[0]>extent[2]) p[0] = extent[2];
|
|
if (p[1]<extent[1]) p[1] = extent[1];
|
|
else if (p[1]>extent[3]) p[1] = extent[3];
|
|
})
|
|
})
|
|
})
|
|
if (poly) {
|
|
return new ol_geom_Polygon(geom[0]);
|
|
} else {
|
|
return new ol_geom_MultiPolygon(geom);
|
|
}
|
|
};
|
|
})();
|
|
|
|
export { ol_extent_intersection }
|
|
export { ol_extent_intersection as extentIntersection }
|
|
|
|
/** Add points along a segment
|
|
* @param {ol_Coordinate} p1
|
|
* @param {ol_Coordinate} p2
|
|
* @param {number} d
|
|
* @param {boolean} start include starting point, default true
|
|
* @returns {Array<ol_Coordinate>}
|
|
*/
|
|
var ol_coordinate_sampleAt = function(p1, p2, d, start) {
|
|
var pts = [];
|
|
if (start!==false) pts.push(p1);
|
|
var dl = ol_coordinate_dist2d(p1,p2);
|
|
if (dl) {
|
|
var nb = Math.round(dl/d);
|
|
if (nb>1) {
|
|
var dx = (p2[0]-p1[0]) / nb;
|
|
var dy = (p2[1]-p1[1]) / nb;
|
|
for (var i=1; i<nb; i++) {
|
|
pts.push([p1[0] + dx*i, p1[1] + dy*i])
|
|
}
|
|
}
|
|
}
|
|
pts.push(p2);
|
|
return pts;
|
|
};
|
|
export { ol_coordinate_sampleAt }
|
|
|
|
/** Sample a LineString at a distance
|
|
* @param {number} d
|
|
* @returns {ol_geom_LineString}
|
|
*/
|
|
ol_geom_LineString.prototype.sampleAt = function(d) {
|
|
var line = this.getCoordinates();
|
|
var result = [];
|
|
for (var i=1; i<line.length; i++) {
|
|
result = result.concat(ol_coordinate_sampleAt(line[i-1], line[i], d, i===1));
|
|
}
|
|
return new ol_geom_LineString(result);
|
|
};
|
|
|
|
/** Sample a MultiLineString at a distance
|
|
* @param {number} d
|
|
* @returns {ol_geom_MultiLineString}
|
|
*/
|
|
ol_geom_MultiLineString.prototype.sampleAt = function(d) {
|
|
var lines = this.getCoordinates();
|
|
var result = [];
|
|
lines.forEach(function(p) {
|
|
var l = [];
|
|
for (var i=1; i<p.length; i++) {
|
|
l = l.concat(ol_coordinate_sampleAt(p[i-1], p[i], d, i===1));
|
|
}
|
|
result.push(l);
|
|
})
|
|
return new ol_geom_MultiLineString(result);
|
|
};
|
|
|
|
/** Sample a Polygon at a distance
|
|
* @param {number} d
|
|
* @returns {ol_geom_Polygon}
|
|
*/
|
|
ol_geom_Polygon.prototype.sampleAt = function(res) {
|
|
var poly = this.getCoordinates();
|
|
var result = [];
|
|
poly.forEach(function(p) {
|
|
var l = [];
|
|
for (var i=1; i<p.length; i++) {
|
|
l = l.concat(ol_coordinate_sampleAt(p[i-1], p[i], res, i===1));
|
|
}
|
|
result.push(l);
|
|
})
|
|
return new ol_geom_Polygon(result);
|
|
};
|
|
|
|
/** Sample a MultiPolygon at a distance
|
|
* @param {number} res
|
|
* @returns {ol_geom_MultiPolygon}
|
|
*/
|
|
ol_geom_MultiPolygon.prototype.sampleAt = function(res) {
|
|
var mpoly = this.getCoordinates();
|
|
var result = [];
|
|
mpoly.forEach(function(poly) {
|
|
var a = [];
|
|
result.push(a);
|
|
poly.forEach(function(p) {
|
|
var l = [];
|
|
for (var i=1; i<p.length; i++) {
|
|
l = l.concat(ol_coordinate_sampleAt(p[i-1], p[i], res, i===1));
|
|
}
|
|
a.push(l);
|
|
})
|
|
});
|
|
return new ol_geom_MultiPolygon(result);
|
|
};
|
|
|
|
/** Intersect a geometry using a circle
|
|
* @param {ol_geom_Geometry} geom
|
|
* @param {number} resolution circle resolution to sample the polygon on the circle, default 1
|
|
* @returns {ol_geom_Geometry}
|
|
*/
|
|
ol_geom_Circle.prototype.intersection = function(geom, resolution) {
|
|
if (geom.sampleAt) {
|
|
var ext = ol_extent_buffer(this.getCenter().concat(this.getCenter()), this.getRadius());
|
|
geom = ol_extent_intersection(ext, geom);
|
|
geom = geom.simplify(resolution);
|
|
var c = this.getCenter();
|
|
var r = this.getRadius();
|
|
//var res = (resolution||1) * r / 100;
|
|
var g = geom.sampleAt(resolution).getCoordinates();
|
|
switch (geom.getType()) {
|
|
case 'Polygon': g = [g];
|
|
// fallthrough
|
|
case 'MultiPolygon': {
|
|
var hasout = false;
|
|
// var hasin = false;
|
|
var result = [];
|
|
g.forEach(function(poly) {
|
|
var a = [];
|
|
result.push(a);
|
|
poly.forEach(function(ring) {
|
|
var l = [];
|
|
a.push(l);
|
|
ring.forEach(function(p) {
|
|
var d = ol_coordinate_dist2d(c, p);
|
|
if (d > r) {
|
|
hasout = true;
|
|
l.push([
|
|
c[0] + r / d * (p[0]-c[0]),
|
|
c[1] + r / d * (p[1]-c[1])
|
|
]);
|
|
} else {
|
|
// hasin = true;
|
|
l.push(p);
|
|
}
|
|
});
|
|
})
|
|
});
|
|
if (!hasout) return geom;
|
|
if (geom.getType() === 'Polygon') {
|
|
return new ol_geom_Polygon(result[0]);
|
|
} else {
|
|
return new ol_geom_MultiPolygon(result);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
console.warn('[ol/geom/Circle~intersection] Unsupported geometry type: '+geom.getType());
|
|
}
|
|
return geom;
|
|
};
|