diff --git a/CHANGELOG.md b/CHANGELOG.md index b84f7bd..7df3b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add support for Google Map Tiles layers. #207 + ### Changed - Update open layers to version 10.4.0. #206 diff --git a/README.md b/README.md index 6f44462..42cd705 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,22 @@ const wmsOpts = { }; const wmsLayer = myMap.addLayer('wms', wmsOpts); +// Adding a Google Map Tiles layer. This requires registering a Google Developer +// account and creating your own Map Tiles API key. See the following +// documentation on creating and restricting API keys. +// "Google strongly recommends that you restrict your API keys by limiting their +// usage to those only APIs needed for your application. Restricting API keys +// adds security to your application by protecting it from unwarranted requests." +// https://developers.google.com/maps/documentation/tile/get-api-key +const googleMapOpts = { + title: 'Google Satellite', + key: 'AIzaSyBEB1xUvz8LG_XexTVKv5ghXEPaCzF9eng', // replace with your API key. + mapType: 'satellite', + visible: true, // defaults to true + base: true, // defaults to false +} +const googleMapLayer = myMap.addLayer('google', googleMapOpts); + // Adding a ArcGIS MapServer tile layer. const arcGISTileOpts = { title: 'StateCityHighway_USA', // defaults to 'arcgis-tile' diff --git a/src/control/Google/GoogleLogoAttribution.css b/src/control/Google/GoogleLogoAttribution.css new file mode 100644 index 0000000..e02fbe7 --- /dev/null +++ b/src/control/Google/GoogleLogoAttribution.css @@ -0,0 +1,18 @@ +.ol-google-logo-attrib.ol-control { + /* By default position the logo above the scale line and to the right of the snapping grid controls. */ + bottom: 2.75em; + left: 3.5em; + + /* Remove the normal grey control background */ + background-color: rgba(255, 255, 255, 0); +} + +@media (max-width: 767px) { + .map-with-ol-side-panel .ol-google-logo-attrib.ol-control:not(.ol-side-panel) { + /* If the side-panel is expanded on small screens, don't let it get pushed out of view. */ + margin-left: 0; + + /* Add a short transition so it doesn't jump so much. */ + transition: margin-left 500ms; + } +} diff --git a/src/control/Google/GoogleLogoAttribution.js b/src/control/Google/GoogleLogoAttribution.js new file mode 100644 index 0000000..5d67b06 --- /dev/null +++ b/src/control/Google/GoogleLogoAttribution.js @@ -0,0 +1,76 @@ +import Control from 'ol/control/Control'; +import Google from 'ol/source/Google'; +import { CLASS_CONTROL, CLASS_UNSELECTABLE } from 'ol/css'; + +import './GoogleLogoAttribution.css'; + +/** + * @classdesc + * OpenLayers GoogleLogoAttribution Control. + * + * @api + */ +class GoogleLogoAttribution extends Control { + + /** + * @param {Options=} opts GoogleLogoAttribution options. + */ + constructor(opts) { + const options = opts || {}; + + // Call the parent control constructor. + super({ + element: document.createElement('div'), + target: options.target, + }); + + // Define the class name. + const className = 'ol-google-logo-attrib'; + + // Add the CSS classes to the control element. + const { element } = this; + element.className = `${className} ${CLASS_UNSELECTABLE} ${CLASS_CONTROL}`; + + const logoImg = document.createElement('img'); + logoImg.style.pointerEvents = 'none'; + logoImg.src = 'https://developers.google.com/static/maps/documentation/images/google_on_white.png'; + + element.appendChild(logoImg); + } + + /** + * @inheritDoc + * @api + */ + setMap(map) { + const oldMap = this.getMap(); + if (map === oldMap) { + return; + } + if (oldMap) { + + // Cleanup the old event listener + if (this.onMapChangeLayerGroups) { + oldMap.getLayerGroup().un('change', this.onMapChangeLayerGroups.listener); + this.onMapChangeLayerGroups = null; + } + } + super.setMap(map); + + if (map) { + + // Toggle the control visibility upon any top-level layer group change + const updateState = () => { + const anyVisibleGoogleLayers = map.getAllLayers().some(layer => layer.isVisible() && typeof layer.getSource === 'function' && layer.getSource() instanceof Google); + + this.element.style.display = anyVisibleGoogleLayers ? 'block' : 'none'; + }; + + this.onMapChangeLayerGroups = map.getLayerGroup().on('change', updateState); + updateState(); + } + } + +} + +export default GoogleLogoAttribution; diff --git a/src/instance/methods/layer.js b/src/instance/methods/layer.js index 7f7edde..78b7fe6 100644 --- a/src/instance/methods/layer.js +++ b/src/instance/methods/layer.js @@ -1,6 +1,7 @@ // Import source types, layer types, and formats. import VectorSource from 'ol/source/Vector'; import Cluster from 'ol/source/Cluster'; +import Google from 'ol/source/Google'; import TileArcGISRest from 'ol/source/TileArcGISRest'; import TileWMS from 'ol/source/TileWMS'; import XYZ from 'ol/source/XYZ'; @@ -19,6 +20,9 @@ import colorStyles, { clusterStyle } from '../../styles'; // Import readFeatures function. import readFeatures from './features'; +// Import GoogleLogoAttribution control. +import GoogleLogoAttribution from '../../control/Google/GoogleLogoAttribution'; + // Set withCredentials to true for all XHR requests made via OpenLayers' // feature loader. Typically farmOS requires authentication in order to // retrieve data from its GeoJSON endpoints. Setting withCredentials to true @@ -103,6 +107,29 @@ function addGeoJSONLayer({ return layer; } +// Add a Google Map Tiles layer to the map. +function addGoogleMapTilesLayer({ + title = 'google-map-tiles', key, mapType = 'satellite', layerTypes = [], language = 'en-US', region = 'US', apiOptions = null, visible = true, base = true, +}) { + const source = new Google({ + key, + mapType, + layerTypes, + language, + region, + apiOptions, + scale: 'scaleFactor2x', + highDpi: true, + }); + const layer = new TileLayer({ + title, + source, + visible, + type: base ? 'base' : 'normal', + }); + return layer; +} + // Add a Tile ArcGIS MapServer layer to the map. function addTileArcGISMapServerLayer({ title = 'arcgis-tile', url, params, visible = true, base = false, attribution = '', crossOrigin = null, @@ -226,6 +253,16 @@ export default function addLayer(type, opts = {}) { } layer = addGeoJSONLayer(opts); } + if (type.toLowerCase() === 'google') { + if (!opts.key) { + throw new Error('Missing a Google Map Tiles API key.'); + } + layer = addGoogleMapTilesLayer(opts); + if (!this.map.getControls().getArray() + .some(control => control instanceof GoogleLogoAttribution)) { + this.map.addControl(new GoogleLogoAttribution()); + } + } if (type.toLowerCase() === 'arcgis-tile') { if (!opts.url) { throw new Error('Missing a ArcGIS MapServer url.');