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

[Feature] Add QMS Google Maps Tiles #4357

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 77 additions & 1 deletion assets/src/modules/config/BaseLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const BaseLayerTypes = createEnum({
'WMTS': 'wmts',
'WMS': 'wms',
'Lizmap': 'lizmap',
'Google': 'google',
});

/**
Expand Down Expand Up @@ -309,6 +310,52 @@ export class BingBaseLayerConfig extends BaseLayerConfig {

}

const googleProperties = {
'title': { type: 'string' },
'mapType': { type: 'string' }
}

const googleOptionalProperties = {
'key': { type: 'string', nullable: true }
}

/**
* Class representing a Google base layer config
* @class
* @augments BaseLayerConfig
*/
export class GoogleBaseLayerConfig extends BaseLayerConfig {
/**
* Create a GOOGLE base layer config based on a config object
* @param {string} name - the base layer name
* @param {object} cfg - the lizmap config object for GOOGLE base layer
* @param {string} cfg.title - the base layer title
* @param {string} cfg.mapType - the base layer mapType
* @param {string} [cfg.key] - the base layer key
*/
constructor(name, cfg) {
if (!cfg || typeof cfg !== "object") {
throw new ValidationError('The cfg parameter is not an Object!');
}

if (Object.getOwnPropertyNames(cfg).length == 0) {
throw new ValidationError('The cfg parameter is empty!');
}

super(name, cfg, googleProperties, googleOptionalProperties)
this._type = BaseLayerTypes.Google;
}

/**
* The Google mapType
* @type {string}
*/
get mapType() {
return this._mapType;
}

}

const wmtsProperties = {
'title': { type: 'string' },
'url': { type: 'string' },
Expand Down Expand Up @@ -745,6 +792,18 @@ const QMSExternalLayer = {
"imagerySet": "Aerial",
"key": "",
},
"google-streets": {
"type" :"google",
"title": "Google Streets",
"mapType": "roadmap",
"key":""
},
"google-satellite": {
"type" :"google",
"title": "Google Satellite",
"mapType": "satellite",
"key":""
}
}

/**
Expand Down Expand Up @@ -839,7 +898,20 @@ export class BaseLayersConfig {
}
// add the apikey to the configuration
Object.assign(extendedCfg[layerTreeItem.name],{key:options["bingKey"]})
} else {
} else if (externalUrl && externalUrl.includes('google.com') && options["googleKey"]){
if (externalUrl.includes('lyrs=m')) {
// roads
extendedCfg[layerTreeItem.name] = structuredClone(QMSExternalLayer["google-streets"])
} else if (externalUrl.includes('lyrs=s')){
// fallback on satellite map
extendedCfg[layerTreeItem.name] = structuredClone(QMSExternalLayer["google-satellite"])
} else {
extendedCfg[layerTreeItem.name] = structuredClone(QMSExternalLayer["google-streets"])
}
// add the apikey to the configuration
Object.assign(extendedCfg[layerTreeItem.name],{key:options["googleKey"]})
}
else {
// layer could be converted to XYZ or WMTS background layers
extendedCfg[layerTreeItem.name] = structuredClone(layerTreeItem.layerConfig.externalAccess);
}
Expand Down Expand Up @@ -995,6 +1067,10 @@ export class BaseLayersConfig {
this._configs.push(new BingBaseLayerConfig(key, blCfg));
this._names.push(key);
break;
case BaseLayerTypes.Google:
this._configs.push(new GoogleBaseLayerConfig(key, blCfg));
this._names.push(key);
break;
case BaseLayerTypes.WMTS:
this._configs.push(new WmtsBaseLayerConfig(key, blCfg));
this._names.push(key);
Expand Down
6 changes: 5 additions & 1 deletion assets/src/modules/config/LayerTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,11 @@ function buildLayerTreeGroupConfigItems(wmsCapaLayerGroup, layersCfg, level) {
const groupItems = buildLayerTreeGroupConfigItems(wmsCapaLayer, layersCfg, level+1);
items.push(new LayerTreeGroupConfig(cfg.name, level+1, groupItems, wmsCapaLayer, cfg));
} else {
items.push(new LayerTreeLayerConfig(cfg.name, level+1, wmsCapaLayer, cfg));
// avoid to add the baseLayers group to the map if doesn't contains any layer.
if(wmsName.toLowerCase() != 'baselayers') {
items.push(new LayerTreeLayerConfig(cfg.name, level+1, wmsCapaLayer, cfg));
}

}
}
return items;
Expand Down
11 changes: 11 additions & 0 deletions assets/src/modules/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import TileGrid from 'ol/tilegrid/TileGrid.js';
import TileWMS from 'ol/source/TileWMS.js';
import XYZ from 'ol/source/XYZ.js';
import BingMaps from 'ol/source/BingMaps.js';
import Google from 'ol/source/Google.js';
import {BaseLayer as LayerBase} from 'ol/layer/Base.js';
import LayerGroup from 'ol/layer/Group.js';
import { Vector as VectorSource } from 'ol/source.js';
Expand Down Expand Up @@ -448,6 +449,16 @@ export default class map extends olMap {
// maxZoom: 19
}),
});
} else if (baseLayerState.type === BaseLayerTypes.Google) {
baseLayer = new TileLayer({
minResolution: layerMinResolution,
maxResolution: layerMaxResolution,
preload: Infinity,
source: new Google({
key: baseLayerState.key,
mapType: baseLayerState.mapType,
}),
});
} else if (baseLayerState.type === BaseLayerTypes.Lizmap) {
if (baseLayerState.layerConfig.cached) {
const parser = new WMTSCapabilities();
Expand Down
32 changes: 31 additions & 1 deletion assets/src/modules/state/BaseLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import EventDispatcher from './../../utils/EventDispatcher.js';
import { LayerConfig } from './../config/Layer.js';
import { AttributionConfig } from './../config/Attribution.js'
import { BaseLayerTypes, BaseLayersConfig, BaseLayerConfig, EmptyBaseLayerConfig, XyzBaseLayerConfig, BingBaseLayerConfig, WmtsBaseLayerConfig, WmsBaseLayerConfig } from './../config/BaseLayer.js';
import { BaseLayerTypes, BaseLayersConfig, BaseLayerConfig, EmptyBaseLayerConfig, XyzBaseLayerConfig, BingBaseLayerConfig, GoogleBaseLayerConfig, WmtsBaseLayerConfig, WmsBaseLayerConfig } from './../config/BaseLayer.js';
import { LayerVectorState, LayerRasterState, LayerGroupState, LayersAndGroupsCollection } from './Layer.js'
import { MapLayerLoadStatus } from './MapLayer.js';

Expand Down Expand Up @@ -267,6 +267,33 @@ export class BingBaseLayerState extends BaseLayerState {
}
}

/**
* Class representing a Google base layer state
* @class
* @augments BaseLayerState
*/
export class GoogleBaseLayerState extends BaseLayerState {
/**
* Create a base layers google state based on the google base layer config
* @param {GoogleBaseLayerConfig} baseLayerCfg - the lizmap google base layer config object
* @param {LayerRasterState} [itemState] - the lizmap google layer layer state
*/
constructor(baseLayerCfg, itemState = null ) {
if (baseLayerCfg.type !== BaseLayerTypes.Google) {
throw new TypeError('Not an `' + BaseLayerTypes.Google + '` base layer config. Get `' + baseLayerCfg.type + '` type for `' + baseLayerCfg.name + '` base layer!');
}
super(baseLayerCfg, itemState)
}

/**
* The google mapType
* @type {string}
*/
get mapType() {
return this._baseLayerConfig.mapType;
}
}

/**
* Class representing an WMTS base layer state
* @class
Expand Down Expand Up @@ -432,6 +459,9 @@ export class BaseLayersState extends EventDispatcher {
case BaseLayerTypes.Bing:
this._baseLayersMap.set(blConfig.name, new BingBaseLayerState(blConfig, itemState));
break;
case BaseLayerTypes.Google:
this._baseLayersMap.set(blConfig.name, new GoogleBaseLayerState(blConfig, itemState));
break;
case BaseLayerTypes.WMTS:
this._baseLayersMap.set(blConfig.name, new WmtsBaseLayerState(blConfig, itemState));
break;
Expand Down
39 changes: 39 additions & 0 deletions tests/end2end/playwright/google-basemap.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

const { test, expect } = require('@playwright/test');
const { gotoMap } = require('./globals');

test.describe('Google Maps Baselayers', () => {
test('Load map with no API Key', async ({ page }) => {
const url = '/index.php/view/map?repository=testsrepository&project=google_basemap';
await gotoMap(url, page);

// baselayers group should be hidden since it is empty due to the default STRICT_GOOGLE_TOS_CHECK env variable value = TRUE
Gustry marked this conversation as resolved.
Show resolved Hide resolved
await expect(page.locator('#switcher-baselayer.hide')).toHaveCount(1);

});

test('Load map with dummy API Key', async ({ page }) => {
// listen to the requests to intercept failing ones
let initGoogleRequestsCount = 0;
page.on('response', response => {
if(response.url().includes('createSession?key=dummy') && response.status() != 200) {
initGoogleRequestsCount++;
}
});

const url = '/index.php/view/map?repository=testsrepository&project=google_apikey_basemap';
await gotoMap(url, page);

// there are three Google base layers in the project, so the expected number of failing requests is three
expect(initGoogleRequestsCount).toBe(3);
// baselayers group should be visible...
await expect(page.locator('#switcher-baselayer')).toBeVisible();

//.. and should contains the three Google base layers (not loaded)
let options = page.locator('#switcher-baselayer').getByRole('combobox').locator('option');
await expect(options).toHaveCount(3);
expect(await options.nth(0).getAttribute('value')).toBe('Google Streets');
expect(await options.nth(1).getAttribute('value')).toBe('Google Satellite');
expect(await options.nth(2).getAttribute('value')).toBe('Google Hybrid');
});
});
Loading
Loading