|
| 1 | +import type { Feature, Map } from 'ol' |
| 2 | +import type { Coordinate } from 'ol/coordinate' |
| 3 | +import VectorLayer from 'ol/layer/Vector' |
| 4 | +import VectorSource from 'ol/source/Vector' |
| 5 | + |
| 6 | +// arbitrarily give up after 10s of stalling (100 * 100ms) |
| 7 | +const readinessCheckLimit = 100 |
| 8 | +const readinessWaitTime = 100 |
| 9 | + |
| 10 | +export const errors = { |
| 11 | + undefinedBoundaryLayer: Symbol.for('Boundary Layer undefined'), |
| 12 | + undefinedBoundarySource: Symbol.for('Boundary Source undefined'), |
| 13 | + sourceNotReady: Symbol.for('Source not ready'), |
| 14 | +} as const |
| 15 | + |
| 16 | +/** |
| 17 | + * @param source - source to check for readiness |
| 18 | + * @returns Promise that resolves true if source is in 'ready' state with at |
| 19 | + * least one feature within time limit; else resolves false. |
| 20 | + */ |
| 21 | +async function isReady(source: VectorSource) { |
| 22 | + let readinessChecks = 0 |
| 23 | + |
| 24 | + while (source.getState() !== 'ready' || source.getFeatures().length === 0) { |
| 25 | + if (readinessChecks++ < readinessCheckLimit) { |
| 26 | + await new Promise((resolve) => { |
| 27 | + setTimeout(resolve, readinessWaitTime) |
| 28 | + }) |
| 29 | + } else { |
| 30 | + return false |
| 31 | + } |
| 32 | + } |
| 33 | + |
| 34 | + return true |
| 35 | +} |
| 36 | + |
| 37 | +/** |
| 38 | + * Checks whether the given coordinate is withing the boundary of the layer that |
| 39 | + * has the given layer id. |
| 40 | + * |
| 41 | + * @returns Resolves true if coordinate is within boundary, false if outside of |
| 42 | + * boundary, and an error symbol if something about the check broke. If no |
| 43 | + * boundaryLayerId is set, it always resolves true, as in "no boundary exists". |
| 44 | + */ |
| 45 | +export async function passesBoundaryCheck( |
| 46 | + map: Map, |
| 47 | + boundaryLayerId: string | undefined, |
| 48 | + coordinate: Coordinate |
| 49 | +) { |
| 50 | + if (typeof boundaryLayerId === 'undefined') { |
| 51 | + return true |
| 52 | + } |
| 53 | + |
| 54 | + const boundaryLayer = map |
| 55 | + .getLayers() |
| 56 | + .getArray() |
| 57 | + .find((layer) => layer.get('id') === boundaryLayerId) |
| 58 | + |
| 59 | + if (!(boundaryLayer instanceof VectorLayer)) { |
| 60 | + console.error( |
| 61 | + `No layer configured to match boundaryLayerId "${boundaryLayerId}".` |
| 62 | + ) |
| 63 | + return errors.undefinedBoundaryLayer |
| 64 | + } |
| 65 | + |
| 66 | + const boundaryLayerSource = boundaryLayer.getSource() |
| 67 | + |
| 68 | + if (!(boundaryLayerSource instanceof VectorSource)) { |
| 69 | + console.error( |
| 70 | + `Layer with boundaryLayerId "${boundaryLayerId}" missing source.` |
| 71 | + ) |
| 72 | + return errors.undefinedBoundarySource |
| 73 | + } |
| 74 | + |
| 75 | + const sourceReady = await isReady(boundaryLayerSource) |
| 76 | + |
| 77 | + if (!sourceReady) { |
| 78 | + console.error( |
| 79 | + `Layer with boundaryLayerId "${boundaryLayerId}" did not load or is featureless.` |
| 80 | + ) |
| 81 | + return errors.sourceNotReady |
| 82 | + } |
| 83 | + |
| 84 | + const features = boundaryLayerSource.getFeatures() as Feature[] |
| 85 | + return features.some((feature) => |
| 86 | + feature.getGeometry()?.intersectsCoordinate(coordinate) |
| 87 | + ) |
| 88 | +} |
0 commit comments