/** * @module ol/geom/flat/orient */ import {coordinates as reverseCoordinates} from './reverse.js'; /** * Is the linear ring oriented clockwise in a coordinate system with a bottom-left * coordinate origin? For a coordinate system with a top-left coordinate origin, * the ring's orientation is clockwise when this function returns false. * @param {Array} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {number} end End. * @param {number} stride Stride. * @return {boolean} Is clockwise. */ export function linearRingIsClockwise(flatCoordinates, offset, end, stride) { // https://stackoverflow.com/q/1165647/clockwise-method#1165943 // https://github.com/OSGeo/gdal/blob/master/gdal/ogr/ogrlinearring.cpp let edge = 0; let x1 = flatCoordinates[end - stride]; let y1 = flatCoordinates[end - stride + 1]; for (; offset < end; offset += stride) { const x2 = flatCoordinates[offset]; const y2 = flatCoordinates[offset + 1]; edge += (x2 - x1) * (y2 + y1); x1 = x2; y1 = y2; } return edge === 0 ? undefined : edge > 0; } /** * Determines if linear rings are oriented. By default, left-hand orientation * is tested (first ring must be clockwise, remaining rings counter-clockwise). * To test for right-hand orientation, use the `right` argument. * * @param {Array} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {Array} ends Array of end indexes. * @param {number} stride Stride. * @param {boolean} [right] Test for right-hand orientation * (counter-clockwise exterior ring and clockwise interior rings). * @return {boolean} Rings are correctly oriented. */ export function linearRingsAreOriented( flatCoordinates, offset, ends, stride, right ) { right = right !== undefined ? right : false; for (let i = 0, ii = ends.length; i < ii; ++i) { const end = ends[i]; const isClockwise = linearRingIsClockwise( flatCoordinates, offset, end, stride ); if (i === 0) { if ((right && isClockwise) || (!right && !isClockwise)) { return false; } } else { if ((right && !isClockwise) || (!right && isClockwise)) { return false; } } offset = end; } return true; } /** * Determines if linear rings are oriented. By default, left-hand orientation * is tested (first ring must be clockwise, remaining rings counter-clockwise). * To test for right-hand orientation, use the `right` argument. * * @param {Array} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {Array>} endss Array of array of end indexes. * @param {number} stride Stride. * @param {boolean} [right] Test for right-hand orientation * (counter-clockwise exterior ring and clockwise interior rings). * @return {boolean} Rings are correctly oriented. */ export function linearRingssAreOriented( flatCoordinates, offset, endss, stride, right ) { for (let i = 0, ii = endss.length; i < ii; ++i) { const ends = endss[i]; if (!linearRingsAreOriented(flatCoordinates, offset, ends, stride, right)) { return false; } if (ends.length) { offset = ends[ends.length - 1]; } } return true; } /** * Orient coordinates in a flat array of linear rings. By default, rings * are oriented following the left-hand rule (clockwise for exterior and * counter-clockwise for interior rings). To orient according to the * right-hand rule, use the `right` argument. * * @param {Array} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {Array} ends Ends. * @param {number} stride Stride. * @param {boolean} [right] Follow the right-hand rule for orientation. * @return {number} End. */ export function orientLinearRings( flatCoordinates, offset, ends, stride, right ) { right = right !== undefined ? right : false; for (let i = 0, ii = ends.length; i < ii; ++i) { const end = ends[i]; const isClockwise = linearRingIsClockwise( flatCoordinates, offset, end, stride ); const reverse = i === 0 ? (right && isClockwise) || (!right && !isClockwise) : (right && !isClockwise) || (!right && isClockwise); if (reverse) { reverseCoordinates(flatCoordinates, offset, end, stride); } offset = end; } return offset; } /** * Orient coordinates in a flat array of linear rings. By default, rings * are oriented following the left-hand rule (clockwise for exterior and * counter-clockwise for interior rings). To orient according to the * right-hand rule, use the `right` argument. * * @param {Array} flatCoordinates Flat coordinates. * @param {number} offset Offset. * @param {Array>} endss Array of array of end indexes. * @param {number} stride Stride. * @param {boolean} [right] Follow the right-hand rule for orientation. * @return {number} End. */ export function orientLinearRingsArray( flatCoordinates, offset, endss, stride, right ) { for (let i = 0, ii = endss.length; i < ii; ++i) { offset = orientLinearRings( flatCoordinates, offset, endss[i], stride, right ); } return offset; } /** * Return a two-dimensional endss * @param {Array} flatCoordinates Flat coordinates * @param {Array} ends Linear ring end indexes * @return {Array>} Two dimensional endss array that can * be used to construct a MultiPolygon */ export function inflateEnds(flatCoordinates, ends) { const endss = []; let offset = 0; let prevEndIndex = 0; for (let i = 0, ii = ends.length; i < ii; ++i) { const end = ends[i]; // classifies an array of rings into polygons with outer rings and holes if (!linearRingIsClockwise(flatCoordinates, offset, end, 2)) { endss.push(ends.slice(prevEndIndex, i + 1)); } else { if (endss.length === 0) { continue; } endss[endss.length - 1].push(ends[prevEndIndex]); } prevEndIndex = i + 1; offset = end; } return endss; }