Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determine Leaflet's base layers by fetching raster-tile-base-layer-configs-version-1.jsonc in photo-location-map-resources repo. #575

Merged
merged 26 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
999e5eb
Fetch the tile server URL from photo-location-map-resources repo.
TomoyukiAota Dec 19, 2024
363910c
Use the latest content of main branch in photo-location-map-resources…
TomoyukiAota Dec 21, 2024
16c8915
Use lowerCamelCase.
TomoyukiAota Dec 21, 2024
3047c1e
Sort alphabetically.
TomoyukiAota Dec 21, 2024
4aee109
Introduce the fallback for TileServerConfig in case of fetching it fa…
TomoyukiAota Dec 21, 2024
4e65576
Improve logging. Introduce the fallback in case of failing to parse t…
TomoyukiAota Dec 21, 2024
9ef1098
Adopt the updated format of osm-tile-server-config-version-1.jsonc in…
TomoyukiAota Dec 26, 2024
b2391db
Update logging.
TomoyukiAota Dec 26, 2024
734fbb8
Update error handling and logging.
TomoyukiAota Dec 26, 2024
4ce1977
Handle configuring the list of layers from the fetched URL (instead o…
TomoyukiAota Dec 26, 2024
efee0e4
Remove unused import.
TomoyukiAota Dec 26, 2024
13ff719
Use "name" instead of "uniqueName" or "displayName". This is because …
TomoyukiAota Dec 26, 2024
b98f04d
Fetch the renamed file with the updated file format for the tile layers.
TomoyukiAota Dec 28, 2024
c99646c
Rename to RasterTileBaseLayerConfigs.
TomoyukiAota Dec 28, 2024
ef40a4b
Rename file.
TomoyukiAota Dec 28, 2024
40cf91e
Add Analytics of fetching RasterTileBaseLayerConfigsVersion1.
TomoyukiAota Dec 28, 2024
df23552
Consolidate recordErrorAndGetFallback.
TomoyukiAota Dec 28, 2024
0547594
Refactor fetchRasterTileBaseLayerConfigsVersion1WithFallback.
TomoyukiAota Dec 28, 2024
3efddf7
Reorder interface/constant. Add comments.
TomoyukiAota Dec 28, 2024
451a10f
Rename constant.
TomoyukiAota Dec 28, 2024
c659d40
Update messages.
TomoyukiAota Dec 28, 2024
5c61fe4
Handle the case that the server responds with an error status like 40…
TomoyukiAota Dec 28, 2024
f238634
Record the invalid configs object.
TomoyukiAota Dec 28, 2024
5bb4064
Remove "maxZoom: 19" since marker cluster does not work well.
TomoyukiAota Dec 28, 2024
5dfa926
Prevent the issue that marker cluster does not work at zoom level 19.
TomoyukiAota Dec 28, 2024
babd853
Refactor how to get defaultBaseLayer.
TomoyukiAota Dec 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions angular.webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as webpack from 'webpack';
// This is Custom Webpack Config Function.
// See https://www.npmjs.com/package/@angular-builders/custom-webpack#custom-webpack-config-function
export default (config: webpack.Configuration, options: CustomWebpackBrowserSchema) => {
config.experiments.topLevelAwait = true;
config.target = 'electron-renderer';
return config;
};
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
"espower-typescript": "10.0.1",
"exif-parser": "0.1.12",
"jasmine-core": "5.2.0",
"jsonc-parser": "3.3.1",
"karma": "6.4.4",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-electron": "7.3.0",
Expand Down
44 changes: 26 additions & 18 deletions src/app/map/leaflet-map/leaflet-map.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PhotoInfoViewerContent } from '../../photo-info-viewer/photo-info-viewe
import { LeafletMapForceRenderService } from './leaflet-map-force-render/leaflet-map-force-render.service';
import { createDivIconHtml } from './div-icon';
import { leafletMapLogger as logger } from './leaflet-map-logger';
import { rasterTileBaseLayerConfigsVersion1 } from './raster-tile-base-layer-configs-version-1';

// References to implement Bing Maps with leaflet-plugins:
// - https://github.com/shramov/leaflet-plugins/blob/master/examples/bing.html
Expand All @@ -39,8 +40,8 @@ export class LeafletMapComponent implements OnDestroy, AfterViewInit {
private pinnedPhotoServiceSubscription: Subscription;
private forceRenderServiceSubscription: Subscription;
private readonly commonLayerOptions = {
maxNativeZoom: 19,
maxZoom: 19,
maxNativeZoom: 18,
maxZoom: 18, // To prevent the issue that marker cluster does not work at zoom level 19.
};
private map: Map;
private regionInfo: RegionInfo;
Expand Down Expand Up @@ -148,18 +149,16 @@ export class LeafletMapComponent implements OnDestroy, AfterViewInit {
}

private configureBaseLayer() {
const bingLayer = this.getBingLayer();
const osmLayer = this.getOsmLayer();
const rasterTileBaseLayers = this.getRasterTileBaseLayers();
const bingLayers = this.getBingLayers();
const baseLayers = {
'Bing (Road)': bingLayer.roadOnDemand,
'Bing (Aerial)': bingLayer.aerial,
'Bing (Aerial with Labels)': bingLayer.aerialWithLabelsOnDemand,
'OpenStreetMap': osmLayer,
...rasterTileBaseLayers,
...bingLayers,
};
L.control.layers(baseLayers, null, {position: 'topright'}).addTo(this.map);

const previousBaseLayer = baseLayers[this.selectedBaseLayerName];
const defaultBaseLayer = bingLayer.roadOnDemand;
const defaultBaseLayer = Object.values(baseLayers)[0];
const selectedBaseLayer = previousBaseLayer ?? defaultBaseLayer;
selectedBaseLayer.addTo(this.map);

Expand All @@ -168,7 +167,19 @@ export class LeafletMapComponent implements OnDestroy, AfterViewInit {
});
}

private getBingLayer() {
private getRasterTileBaseLayers() {
const tileLayers = {};
rasterTileBaseLayerConfigsVersion1.rasterTileBaseLayerConfigs.forEach(config => {
const tileLayer = L.tileLayer(config.url, {
...config.options,
maxZoom: 18, // To prevent the issue that marker cluster does not work at zoom level 19.
});
tileLayers[config.name] = tileLayer;
});
return tileLayers;
}

private getBingLayers() {
const bingMapsKey = '96S0sLgTrpX5VudevEyg~93qOp_-tPdiBcUw_Q-mpUg~AtbViWkzvmAlU9MB08o4mka92JlnRQnYHrHP8GKZBbl0caebqVS95jsvOKVHvrt3';
const bingMapsOptions = {
key: bingMapsKey,
Expand All @@ -178,7 +189,11 @@ export class LeafletMapComponent implements OnDestroy, AfterViewInit {
const roadOnDemand = new L.bingLayer(L.extend({imagerySet: 'RoadOnDemand'}, bingMapsOptions));
const aerial = new L.bingLayer(L.extend({imagerySet: 'Aerial'}, bingMapsOptions));
const aerialWithLabelsOnDemand = new L.bingLayer(L.extend({imagerySet: 'AerialWithLabelsOnDemand'}, bingMapsOptions));
return {roadOnDemand, aerial, aerialWithLabelsOnDemand};
return {
'Bing (Road)': roadOnDemand,
'Bing (Aerial)': aerial,
'Bing (Aerial with Labels)': aerialWithLabelsOnDemand,
};
}

private getCultureForBingMaps(): string {
Expand All @@ -191,13 +206,6 @@ export class LeafletMapComponent implements OnDestroy, AfterViewInit {
return navigator.language;
}

private getOsmLayer() {
return L.tileLayer('https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors, CC-BY-SA',
...this.commonLayerOptions,
});
}

private configureRegionSelector() {
this.configureRegionInfo();
this.configureLeafletGeoman();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { parse as parseJsonc } from 'jsonc-parser';
import { Analytics } from '../../../../src-shared/analytics/analytics';
import { toLoggableString } from '../../../../src-shared/log/to-loggable-string';
import { leafletMapLogger as logger } from './leaflet-map-logger';

// Represents the content of raster-tile-base-layer-configs-version-1.jsonc in photo-location-map-resources repo.
interface RasterTileBaseLayerConfigsVersion1 {
version: string;
rasterTileBaseLayerConfigs: RasterTileBaseLayerConfigs[];
}

interface RasterTileBaseLayerConfigs {
name: string;
url: string;
options?: any;
}

// The fallback configs used in case of failing to fetch the configs from photo-location-map-resources repo.
export const rasterTileBaseLayerConfigsVersion1Fallback: RasterTileBaseLayerConfigsVersion1 = {
version: '1',
rasterTileBaseLayerConfigs: [
{
name: 'OpenStreetMap',
url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
options: {
attribution: '© OpenStreetMap contributors'
}
},
{
name: 'Esri World Street Map',
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
options: {
attribution: 'Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
}
},
{
name: 'Esri World Imagery',
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
options: {
attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
}
}
],
};

// In the production environment, the content of main branch in photo-location-map-resources repo is used.
// In the development environment, the content of the feature branch can be used as needed.
const configsFileUrl
= 'https://cdn.jsdelivr.net/gh/TomoyukiAota/photo-location-map-resources@main/map-configs/raster-tile-base-layer-configs-version-1.jsonc';

async function fetchRasterTileBaseLayerConfigsVersion1(): Promise<RasterTileBaseLayerConfigsVersion1> {
const response = await fetch(configsFileUrl);
if (!response.ok) {
throw new Error(`response.status: ${response.status}, response.statusText: ${response.statusText}`);
}
const jsonc = await response.text();
const json = parseJsonc(jsonc);
return json as RasterTileBaseLayerConfigsVersion1;
}

function recordErrorAndGetFallback(message: string): RasterTileBaseLayerConfigsVersion1 {
logger.error(message);
Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fallback BaseLayerConfigs`, message);
return rasterTileBaseLayerConfigsVersion1Fallback;
}

async function fetchRasterTileBaseLayerConfigsVersion1WithFallback(): Promise<RasterTileBaseLayerConfigsVersion1> {
const fetchingMessage = `Fetching ${configsFileUrl}`;
logger.info(fetchingMessage);
Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetching BaseLayerConfigs`, fetchingMessage);

let configs: RasterTileBaseLayerConfigsVersion1;
try {
configs = await fetchRasterTileBaseLayerConfigsVersion1();
} catch (error) {
const message = `Failed to fetch ${configsFileUrl}. Using the fallback configs. ${error.message}`;
return recordErrorAndGetFallback(message);
}

if (!configs?.rasterTileBaseLayerConfigs?.length) {
const message = `Invalid configs object is fetched from ${configsFileUrl}. Using the fallback configs.\n${toLoggableString(configs)}`;
return recordErrorAndGetFallback(message);
}

const fetchedMessage = `Fetched ${configsFileUrl}\n${toLoggableString(configs)}`;
logger.info(fetchedMessage);
Analytics.trackEvent('Leaflet Map', `[Leaflet Map] Fetched BaseLayerConfigs`, fetchedMessage);
return configs;
}

export const rasterTileBaseLayerConfigsVersion1 = await fetchRasterTileBaseLayerConfigsVersion1WithFallback();
Loading