Skip to content

Commit e0bcb78

Browse files
committed
Fix offline routing for mapeak and ihm support
1 parent f4c23ec commit e0bcb78

4 files changed

Lines changed: 76 additions & 9 deletions

File tree

IsraelHiking.Web/src/application/services/offline-files-download.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ export class OfflineFilesDownloadService {
240240
for (const fileName of fileNames) {
241241
this.loggingService.info(`[Offline Download] Deleting file ${fileName}`);
242242
await this.fileService.deleteFileInDataDirectory(fileName);
243+
this.pmtilesService.invalidateFile(fileName);
243244
}
244245
}
245246
downloadedTiles = this.store.selectSnapshot((s: ApplicationState) => s.offlineState.downloadedTiles);

IsraelHiking.Web/src/application/services/pmtiles.service.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,27 @@ export class PmTilesService {
3636
private readonly loggingService = inject(LoggingService);
3737
private readonly store = inject(Store);
3838

39+
/**
40+
* Creates a source for the given file, throws when the file does not exist.
41+
* Only existing files are cached, so a file that was downloaded later will be picked up.
42+
*/
3943
private async getSource(filePath: string): Promise<Source> {
4044
if (this.sourcesCache.has(filePath)) {
4145
return this.sourcesCache.get(filePath);
4246
}
47+
await Filesystem.stat({ path: filePath, directory: Directory.Data });
4348
const source = new CapacitorSource(filePath);
4449
this.sourcesCache.set(filePath, source);
4550
return source;
4651
}
4752

53+
/**
54+
* Removes a file from the sources cache, should be called when a file is deleted.
55+
*/
56+
public invalidateFile(fileName: string): void {
57+
this.sourcesCache.delete(fileName);
58+
}
59+
4860
/**
4961
* Get's a tile from the stored pmtiles file
5062
* @param url - should be something like custom://filename-without-pmtiles-extention/{z}/{x}/{y}.png
@@ -100,7 +112,7 @@ export class PmTilesService {
100112
try {
101113
await this.getSource(fileName);
102114
} catch (ex) {
103-
this.loggingService.error(`Failed to open file ${fileName} for tile ${tileX}-${tileY} type ${type} and ${z}/${x}/${y}: ${(ex as Error).message}`);
115+
this.loggingService.debug(`Failed to open file ${fileName} for tile ${tileX}-${tileY} type ${type} and ${z}/${x}/${y}: ${(ex as Error).message}`);
104116
return false;
105117
}
106118
return true;

IsraelHiking.Web/src/application/services/routing.provider.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,53 @@ describe("RoutingProvider", () => {
369369
)
370370
);
371371

372+
it("Should fall back to the older schema when the newer schema is missing for one of the tiles",
373+
inject([RoutingProvider, HttpTestingController, PmTilesService],
374+
async (router: RoutingProvider, mockBackend: HttpTestingController, db: PmTilesService) => {
375+
const featureCollection = {
376+
type: "FeatureCollection",
377+
features: [
378+
{
379+
type: "Feature",
380+
geometry: {
381+
type: "LineString",
382+
coordinates: [
383+
[35.0001, 32.0001],
384+
[35.0001, 32.0003],
385+
[35.0003, 32.0003]
386+
]
387+
},
388+
properties: {
389+
ihm_class: "track"
390+
}
391+
}
392+
]
393+
} as GeoJSON.FeatureCollection;
394+
395+
let mapeakAvailabilityCalls = 0;
396+
db.isOfflineFileAvailable = (_z, _x, _y, type) => {
397+
if (type !== "mapeak-schema") {
398+
return Promise.resolve(true);
399+
}
400+
mapeakAvailabilityCalls++;
401+
return Promise.resolve(mapeakAvailabilityCalls === 1);
402+
};
403+
const usedSchemas: string[] = [];
404+
db.getTileByType = (_z, _x, _y, type) => {
405+
usedSchemas.push(type);
406+
return Promise.resolve(createTileFromFeatureCollection(featureCollection));
407+
};
408+
409+
const promise = router.getRoute({ lat: 32.0001, lng: 35.0001 }, { lat: 32.0003, lng: 35.0003 }, "Hike");
410+
411+
mockBackend.expectOne(() => true).flush(null, { status: 500, statusText: "Server error" });
412+
const data = await promise;
413+
expect(data.length).toBeGreaterThan(1);
414+
expect(usedSchemas.every(s => s === "IHM-schema")).toBe(true);
415+
}
416+
)
417+
);
418+
372419
it("Should return start and end point when all lines are filtered out",
373420
inject([RoutingProvider, HttpTestingController, PmTilesService, Store],
374421
async (router: RoutingProvider, mockBackend: HttpTestingController, db: PmTilesService, store: Store) => {

IsraelHiking.Web/src/application/services/routing.provider.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,19 @@ export class RoutingProvider {
8383
if (tileXmax - tileXmin > 2 || tileYmax - tileYmin > 2) {
8484
throw new Error("Offline routing is only supported for adjecent tiles maximum...");
8585
}
86-
let schema = RoutingProvider.IHM_ROUTING_SCHEMA;
87-
for (const tile of tiles) {
88-
if (!await this.pmTilesService.isOfflineFileAvailable(zoom, tile.x, tile.y, RoutingProvider.IHM_ROUTING_SCHEMA)
89-
&& !await this.pmTilesService.isOfflineFileAvailable(zoom, tile.x, tile.y, RoutingProvider.MAPEAK_ROUTING_SCHEMA)) {
90-
throw new Error("Unable to find offline route, some tiles are missing");
91-
}
92-
if (await this.pmTilesService.isOfflineFileAvailable(zoom, tile.x, tile.y, RoutingProvider.MAPEAK_ROUTING_SCHEMA)) {
93-
schema = RoutingProvider.MAPEAK_ROUTING_SCHEMA;
86+
// A schema is usable only when all the relevant tiles are available in it, prefer the newer schema.
87+
let schema: string = null;
88+
for (const schemaCandidate of [RoutingProvider.MAPEAK_ROUTING_SCHEMA, RoutingProvider.IHM_ROUTING_SCHEMA]) {
89+
const availability = await Promise.all(
90+
tiles.map(tile => this.pmTilesService.isOfflineFileAvailable(zoom, tile.x, tile.y, schemaCandidate)));
91+
if (availability.every(available => available)) {
92+
schema = schemaCandidate;
93+
break;
9494
}
9595
}
96+
if (schema == null) {
97+
throw new Error("Unable to find offline route, some tiles are missing");
98+
}
9699
// increase the chance of getting a route by adding more tiles
97100
if (tileXmax === tileXmin) {
98101
tileXmax += 1;
@@ -136,6 +139,10 @@ export class RoutingProvider {
136139
allCollection.push(this.featuresCache.get(key));
137140
continue;
138141
}
142+
// The tiles range is extended beyond the start and end tiles, so some of them might not be available.
143+
if (!await this.pmTilesService.isOfflineFileAvailable(zoom, tileX, tileY, schema)) {
144+
continue;
145+
}
139146
const collection = {
140147
type: "FeatureCollection",
141148
features: []

0 commit comments

Comments
 (0)