Skip to content

Commit

Permalink
Merge pull request 3liz#4357 from mind84/google_wmts
Browse files Browse the repository at this point in the history
[Feature] Add QMS Google Maps Tiles
  • Loading branch information
Gustry authored Jun 21, 2024
2 parents 4fec8cc + e592f4d commit 5f38e56
Show file tree
Hide file tree
Showing 9 changed files with 3,310 additions and 3 deletions.
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
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

0 comments on commit 5f38e56

Please sign in to comment.