Skip to content

Commit

Permalink
Added source tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ponlawat-w committed Nov 24, 2023
1 parent cc95285 commit 1b42ac2
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/source/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default abstract class OSMOverpassSourceBase<GeometryType extends OSMGeom
this.clear(true);
this.loadedExtents.clear();
}
const features = await this.fetchOSMOverpass(extent, projection);
const features = await this.fetchOSMOverpass(fetchExtent, projection);
this.addFeatures(features.filter(x => !this.getFeatureById(x.getId()!)));
this.loadedExtents.insert(fetchExtent, { extent: fetchExtent });
return success && success(features);
Expand Down
2 changes: 2 additions & 0 deletions tests/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const OVERPASS_API_ENDPOINT = 'https://overpass-api.de/api/interpreter';
export const OVERPASS_FETCH_TIMEOUT = 5000;
File renamed without changes.
File renamed without changes.
307 changes: 307 additions & 0 deletions tests/source-node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
import { beforeAll, beforeEach, describe, expect, it, vi, test } from 'vitest';
import { Map, View } from 'ol';
import { Vector as VectorLayer } from 'ol/layer';
import { OSMOverpassNodeSource, Overpass } from '../src';
import { Window } from 'happy-dom';
import { boundingExtent, buffer, containsCoordinate } from 'ol/extent';
import { Projection, transform, transformExtent } from 'ol/proj';
import { OVERPASS_API_ENDPOINT, OVERPASS_FETCH_TIMEOUT } from './common';

describe('Test OSMOverpassNodeSource', () => {
const epsg4326 = new Projection({ code: 'EPSG:4326' });

let window: Window;
let map: Map;
let view: View;

beforeAll(() => {
Overpass.endpointURL = OVERPASS_API_ENDPOINT;
});

beforeEach(() => {
window = new Window({
width: 1600,
height: 900
});
const document = window.document;

document.body.innerHTML = '<div id="map" style="width: 1600px; height: 900px;"></div>';

view = new View();
view.fit(boundingExtent([
transform([98.97670, 18.78093], epsg4326, view.getProjection()),
transform([98.99458, 18.79623], epsg4326, view.getProjection())
]));

map = new Map({
target: 'map',
view,
layers: []
});
});

it('fetches nodes', async() => {
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
transform([98.97102, 18.77532], epsg4326, map.getView().getProjection()),
transform([99.00217, 18.80194], epsg4326, map.getView().getProjection())
])),
overpassQuery: 'node["amenity"="restaurant"];'
});
let featuresLoaded = false;
let featuresLoadStarted = false;
source.on('featuresloadstart', () => featuresLoadStarted = true);
source.on('featuresloadend', () => featuresLoaded = true);

const layer = new VectorLayer<OSMOverpassNodeSource>({ source });
map.addLayer(layer);

const extent = view.getViewStateAndExtent().extent;
source.loadFeatures(extent, view.getResolution()!, view.getProjection());

expect(featuresLoadStarted).toBeTruthy();

await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });
expect(source.getFeatures().length).toBeGreaterThan(0);
expect(source.getFeatures().map(f => f.getGeometry()!).every(g => containsCoordinate(extent, g.getFirstCoordinate())));
});

it('does not fetch node when the map view is too big (options.maximumResolution)', () => {
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
transform([98.98231, 18.78593], epsg4326, map.getView().getProjection()),
transform([98.98752, 18.78972], epsg4326, map.getView().getProjection())
])),
overpassQuery: 'node["amenity"="restaurant"];'
});
let featuresLoadStarted = false;
source.on('featuresloadstart', () => featuresLoadStarted = true);

const layer = new VectorLayer<OSMOverpassNodeSource>({ source });
map.addLayer(layer);

const extent = view.getViewStateAndExtent().extent;
source.loadFeatures(extent, view.getResolution()!, view.getProjection());

expect(featuresLoadStarted).toBeFalsy();
});

it('fetches node with buffered extent (options.fetchBufferSize)', async() => {
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
transform([98.97102, 18.77532], epsg4326, map.getView().getProjection()),
transform([99.00217, 18.80194], epsg4326, map.getView().getProjection())
])),
fetchBufferSize: 150,
overpassQuery: 'node["amenity"="restaurant"];'
});
const viewExtent = view.getViewStateAndExtent().extent;
const bufferedExtent = buffer([...viewExtent], source.options.fetchBufferSize);

let featuresLoaded = false;
source.on('featuresloadend', () => featuresLoaded = true);

const layer = new VectorLayer<OSMOverpassNodeSource>({ source });
map.addLayer(layer);

source.loadFeatures(viewExtent, view.getResolution()!, view.getProjection());

await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

expect(source.getFeatures().length).toBeGreaterThan(0);
expect(
source.getFeatures()
.map(f => f.getGeometry()!.getFirstCoordinate())
.every(c => containsCoordinate(bufferedExtent, c))
).toBeTruthy();
expect(
source.getFeatures()
.map(f => f.getGeometry()!.getFirstCoordinate())
.some(c => !containsCoordinate(viewExtent, c))
).toBeTruthy();
});

it('does not fetch when map panned within buffer', async() => {
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
transform([98.97713, 18.78098], epsg4326, view.getProjection()),
transform([98.99402, 18.79558], epsg4326, view.getProjection())
])),
fetchBufferSize: 500,
overpassQuery: 'node["amenity"="restaurant"];'
});
let featuresLoadEnded = false;
let featuresLoadStarted = false;
let featuresLoadedCount = 0;
source.on('featuresloadstart', () => featuresLoadStarted = true);
source.on('featuresloadend', () => { featuresLoadEnded = true; featuresLoadedCount++; });

const layer = new VectorLayer<OSMOverpassNodeSource>({ source });
map.addLayer(layer);

// initial view, should fetch
view.fit(boundingExtent([
transform([98.98471, 18.78555], epsg4326, view.getProjection()),
transform([98.98814, 18.78836], epsg4326, view.getProjection())
]));

source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());

expect(featuresLoadStarted).toBeTruthy();
await vi.waitUntil(() => featuresLoadEnded, { timeout: OVERPASS_FETCH_TIMEOUT });
expect(featuresLoadedCount).toEqual(1);

// moved view within fetched buffer, should not fetch
view.fit(boundingExtent([
transform([98.98355, 18.78620], epsg4326, view.getProjection()),
transform([98.98455, 18.78720], epsg4326, view.getProjection())
]));

featuresLoadStarted = false;
featuresLoadEnded = false;
source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());

expect(featuresLoadStarted).toBeFalsy();
expect(featuresLoadedCount).toEqual(1);

// moved view outside fetched buffer, should fetch
view.fit(boundingExtent([
transform([98.96144, 18.78716], epsg4326, view.getProjection()),
transform([98.96415, 18.78950], epsg4326, view.getProjection())
]));

featuresLoadStarted = false;
featuresLoadEnded = false;
source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());

expect(featuresLoadStarted).toBeTruthy();
await vi.waitUntil(() => featuresLoadEnded, { timeout: OVERPASS_FETCH_TIMEOUT });
expect(featuresLoadedCount).toEqual(2);
});

it('stores caches', async() => {
const initialExtent = boundingExtent([
transform([98.98148, 18.78811], epsg4326, view.getProjection()),
transform([98.98234, 18.78882], epsg4326, view.getProjection())
]);
const movedExtent = boundingExtent([
transform([98.97928, 18.78822], epsg4326, view.getProjection()),
transform([98.98015, 18.78880], epsg4326, view.getProjection())
]);
const initialExtentNodesCount = (await Overpass.fetchInExtent(
transformExtent(initialExtent, view.getProjection(), epsg4326), 'node;')
).elements.length;
const movedExtentNodesCount = (await Overpass.fetchInExtent(
transformExtent(movedExtent, view.getProjection(), epsg4326), 'node;')
).elements.length;
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
transform([98.97713, 18.78098], epsg4326, view.getProjection()),
transform([98.99402, 18.79558], epsg4326, view.getProjection())
])),
fetchBufferSize: 0,
overpassQuery: 'node;',
cachedFeaturesCount: (initialExtentNodesCount + movedExtentNodesCount) * 2
});
let featuresLoaded = false;
source.on('featuresloadend', () => featuresLoaded = true);
const layer = new VectorLayer<OSMOverpassNodeSource>({ source });
map.addLayer(layer);

view.fit(initialExtent);
source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());
await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

const selectedId = source.getFeatures()[0].getId();

featuresLoaded = false;
view.fit(movedExtent);
source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());
await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

expect(source.getFeatures().some(f => f.getId() === selectedId)).toBeTruthy();
});

it('clears caches when exceeds', async() => {
const initialExtent = boundingExtent([
transform([98.98148, 18.78811], epsg4326, view.getProjection()),
transform([98.98234, 18.78882], epsg4326, view.getProjection())
]);
const movedExtent = boundingExtent([
transform([98.97928, 18.78822], epsg4326, view.getProjection()),
transform([98.98015, 18.78880], epsg4326, view.getProjection())
]);
const initialExtentNodesCount = (await Overpass.fetchInExtent(
transformExtent(initialExtent, view.getProjection(), epsg4326), 'node;')
).elements.length;
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
transform([98.97713, 18.78098], epsg4326, view.getProjection()),
transform([98.99402, 18.79558], epsg4326, view.getProjection())
])),
fetchBufferSize: 0,
overpassQuery: 'node;',
cachedFeaturesCount: initialExtentNodesCount - 1
});
let featuresLoaded = false;
source.on('featuresloadend', () => featuresLoaded = true);
const layer = new VectorLayer<OSMOverpassNodeSource>({ source });
map.addLayer(layer);

view.fit(initialExtent);
source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());
await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

const selectedId = source.getFeatures()[0].getId();

featuresLoaded = false;
view.fit(movedExtent);
source.loadFeatures(view.getViewStateAndExtent().extent, view.getResolution()!, view.getProjection());
await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

expect(source.getFeatures().every(f => f.getId() !== selectedId)).toBeTruthy();
});
});

test('Test OSMOverpassNodeSource on ESPG:4326', async() => {
const window = new Window({ width: 1600, height: 900 });
const document = window.document;
document.body.innerHTML = '<div id="map" style="width: 1600px; height: 900px;"></div>';

const view = new View({ projection: 'EPSG:4326' });
view.setViewportSize([1600, 900]);
view.fit(boundingExtent([
[98.98190, 18.78549],
[98.98904, 18.79184]
]));
const source = new OSMOverpassNodeSource({
maximumResolution: view.getResolutionForExtent(boundingExtent([
[98.97217, 18.77835],
[98.99875, 18.79946]
])),
fetchBufferSize: 0.0001,
overpassEndpointURL: OVERPASS_API_ENDPOINT,
overpassQuery: 'node["amenity"="restaurant"];'
});
const layer = new VectorLayer<OSMOverpassNodeSource>({ source });

const map = new Map({
target: 'map',
view,
layers: [layer]
});

let featuresLoaded = false;
source.on('featuresloadend', () => featuresLoaded = true);

source.loadFeatures(view.getViewStateAndExtent().extent, map.getView().getResolution()!, view.getProjection());
await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

expect(source.getFeatures().length).toBeGreaterThan(0);
const bufferedExtent = buffer(view.getViewStateAndExtent().extent, source.options.fetchBufferSize);
expect(
source.getFeatures()
.map(f => f.getGeometry()!.getFirstCoordinate())
.every(c => containsCoordinate(bufferedExtent, c))
).toBeTruthy();
});
49 changes: 49 additions & 0 deletions tests/source-way.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Window } from 'happy-dom';
import { OVERPASS_API_ENDPOINT, OVERPASS_FETCH_TIMEOUT } from './common';
import { Map, View } from 'ol';
import { boundingExtent, containsCoordinate } from 'ol/extent';
import { Projection, transform } from 'ol/proj';
import { expect, test, vi } from 'vitest';
import { OSMOverpassWaySource } from '../src';
import VectorLayer from 'ol/layer/Vector';

test('Test OSMOverpassWaySource', async() => {
const epsg4326 = new Projection({ code: 'EPSG:4326' });
const window = new Window({ width: 1600, height: 900 });
const document = window.document;
document.body.innerHTML = '<div id="map" style="width: 1600px; height: 900px;"></div>';

const view = new View();
const initialExtent = boundingExtent([
transform([98.98472, 18.78784], epsg4326, view.getProjection()),
transform([98.98602, 18.78885], epsg4326, view.getProjection())
]);
view.fit(initialExtent);

const source = new OSMOverpassWaySource({
maximumResolution: view.getResolutionForExtentInternal(boundingExtent([
transform([98.97715, 18.78114], epsg4326, view.getProjection()),
transform([98.99414, 18.79575], epsg4326, view.getProjection())
])),
fetchBufferSize: 0,
overpassQuery: '(way["highway"];>;);',
overpassEndpointURL: OVERPASS_API_ENDPOINT
});
let featuresLoaded = false;
source.on('featuresloadend', () => featuresLoaded = true);
const layer = new VectorLayer({ source });

const map = new Map({
target: 'map',
view, layers: [layer]
});

source.loadFeatures(view.getViewStateAndExtent().extent, map.getView().getResolution()!, view.getProjection());
await vi.waitUntil(() => featuresLoaded, { timeout: OVERPASS_FETCH_TIMEOUT });

expect(source.getFeatures().length).toBeGreaterThan(0);
expect(
source.getFeatures().map(f => f.getGeometry()!)
.some(g => g.getCoordinates().some(c => containsCoordinate(initialExtent, c)))
).toBeTruthy();
});
5 changes: 3 additions & 2 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
include: ['**\/tests\/*.ts'],
environment: 'happy-dom'
include: ['**\/tests\/*.test.ts'],
environment: 'happy-dom',
testTimeout: 60000
}
});

0 comments on commit 1b42ac2

Please sign in to comment.