Skip to content

Map Field: Fall back to Leaflet.js when no Google API key is provided #1137

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

Open
wants to merge 1 commit into
base: development
Choose a base branch
from
Open
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
10 changes: 8 additions & 2 deletions core/Field/Map_Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ public function __construct( $type, $name, $label ) {
*/
public static function admin_enqueue_scripts() {
$api_key = apply_filters( 'carbon_fields_map_field_api_key', false );
$url = apply_filters( 'carbon_fields_map_field_api_url', '//maps.googleapis.com/maps/api/js?' . ( $api_key ? 'key=' . $api_key : '' ), $api_key );

wp_enqueue_script( 'carbon-google-maps', $url, array(), null );
if( $api_key ) {
$url = apply_filters( 'carbon_fields_map_field_api_url', '//maps.googleapis.com/maps/api/js?' . ( $api_key ? 'key=' . $api_key : '' ), $api_key );
wp_enqueue_script( 'carbon-google-maps', $url, array(), null );
} else {
// Use Leaflet.js as a fallback when no Google Maps API key is set
wp_enqueue_style( 'carbon-leaflet', '//unpkg.com/[email protected]/dist/leaflet.css', array(), null );
wp_enqueue_script( 'carbon-leaflet', '//unpkg.com/[email protected]/dist/leaflet.js', array(), null );
}
}

/**
Expand Down
22 changes: 22 additions & 0 deletions packages/core/fields/map/google-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@
*/
import observeResize from 'observe-resize';
import { Component, createRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

export async function googleGeocode( address ) {
const geocoder = new window.google.maps.Geocoder();

return new Promise( ( resolve, reject ) => {
geocoder.geocode( { address }, ( results, status ) => {
if ( status === window.google.maps.GeocoderStatus.OK ) {
const { location } = results[ 0 ].geometry;

resolve( {
lat: location.lat(),
lng: location.lng()
} );
} else if ( status === 'ZERO_RESULTS' ) {
reject( __( 'The address could not be found.', 'carbon-fields-ui' ) );
} else {
reject( `${ __( 'Geocode was not successful for the following reason: ', 'carbon-fields-ui' ) } ${ status }` );
}
} );
} );
}

class GoogleMap extends Component {
/**
Expand Down
33 changes: 9 additions & 24 deletions packages/core/fields/map/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ import {
*/
import './style.scss';
import SearchInput from '../../components/search-input';
import GoogleMap from './google-map';
import GoogleMap, { googleGeocode } from './google-map';
import LeafletMap, { nominatimGeocode } from './leaflet-map';

const isGoogleMapsLoaded = Boolean( document.querySelector( 'script#carbon-google-maps-js' ) );

const Map = isGoogleMapsLoaded ? GoogleMap : LeafletMap;
const geocode = isGoogleMapsLoaded ? googleGeocode : nominatimGeocode;

class MapField extends Component {
/**
Expand All @@ -30,7 +36,7 @@ class MapField extends Component {
if ( address ) {
this.props.onGeocodeAddress( { address } );
}
}, 250 )
}, 500 )

/**
* Handles the change of map location.
Expand Down Expand Up @@ -73,7 +79,7 @@ class MapField extends Component {
onChange={ this.handleSearchChange }
/>

<GoogleMap
<Map
className="cf-map__canvas"
lat={ value.lat }
lng={ value.lng }
Expand Down Expand Up @@ -149,27 +155,6 @@ function handler( props ) {

switch ( type ) {
case 'GEOCODE_ADDRESS':
const geocode = ( address ) => {
return new Promise( ( resolve, reject ) => {
const geocoder = new window.google.maps.Geocoder();

geocoder.geocode( { address }, ( results, status ) => {
if ( status === window.google.maps.GeocoderStatus.OK ) {
const { location } = results[ 0 ].geometry;

resolve( {
lat: location.lat(),
lng: location.lng()
} );
} else if ( status === 'ZERO_RESULTS' ) {
reject( __( 'The address could not be found.', 'carbon-fields-ui' ) );
} else {
reject( `${ __( 'Geocode was not successful for the following reason: ', 'carbon-fields-ui' ) } ${ status }` );
}
} );
} );
};

geocode( payload.address )
.then( ( { lat, lng } ) => {
onChange( id, {
Expand Down
148 changes: 148 additions & 0 deletions packages/core/fields/map/leaflet-map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Based on `google-maps.js`, adapted for Leaflet.js
*/

// External dependencies
import observeResize from 'observe-resize';
import { Component, createRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

export async function nominatimGeocode( address ) {
// Usage Policy: https://operations.osmfoundation.org/policies/nominatim/
const nominatimSearchResults = await new Promise( ( resolve, reject ) => {
const request = window.jQuery.ajax( {
url: 'https://nominatim.openstreetmap.org/search',
type: 'GET',
data: { q: address, format: 'json', limit: 1 },
headers: { 'User-Agent': 'Carbon Fields Map Field' }
} );

request.done( resolve );
request.fail( () => {
reject( __( 'An error occured.', 'carbon-fields-ui' ) );
} );
} );

if ( nominatimSearchResults?.length !== 1 ) {
throw __( 'The address could not be found.', 'carbon-fields-ui' );
}

const { lat, lon: lng } = nominatimSearchResults[ 0 ];
return { lat, lng };
}

class LeafletMap extends Component {
/**
* Keeps references to the DOM node.
*
* @type {Object}
*/
node = createRef();

/**
* Lifecycle hook.
*
* @return {void}
*/
componentDidMount() {
this.setupMap();
this.setupMapEvents();
}

/**
* Lifecycle hook.
*
* @return {void}
*/
componentDidUpdate() {
const {
lat,
lng,
zoom
} = this.props;

const markerPosition = this.marker.getLatLng();
if ( ! markerPosition.equals( [ lat, lng ] ) ) {
this.marker.setLatLng( [ lat, lng ] );
this.map.panTo( [ lat, lng ] );
}

const mapZoom = this.map.getZoom();
if ( zoom !== mapZoom ) {
this.map.setZoom( zoom );
}
}

/**
* Lifecycle hook.
*
* @return {void}
*/
componentWillUnmount() {
this.cancelResizeObserver();
this.map.remove();
}

/**
* Initializes the map into placeholder element.
*
* @return {void}
*/
setupMap() {
const { lat, lng, zoom } = this.props;
const Leaflet = window.L;

this.map = Leaflet.map( this.node.current, {
center: [ lat, lng ],
zoom,
scrollWheelZoom: false
} );

Leaflet.tileLayer( 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
} ).addTo( this.map );

this.marker = Leaflet.marker( [ lat, lng ], {
draggable: true
} ).addTo( this.map );

this.cancelResizeObserver = observeResize( this.node.current, () => {
this.map.invalidateSize();
} );
}

/**
* Adds the listeners for the map's events.
*
* @return {void}
*/
setupMapEvents() {
// Enable scroll wheel zoom on focus
this.map.once( 'focus', () => {
this.map.scrollWheelZoom.enable();
} );

// Update zoom when changed
this.map.on( 'zoomend', () => {
this.props.onChange( { zoom: this.map.getZoom() } );
} );

// Update the position when the marker is moved
this.marker.on( 'dragend', () => {
const position = this.marker.getLatLng();
this.map.panTo( position );
this.props.onChange( { lat: position.lat, lng: position.lng } );
} );
}

/**
* Renders the component.
*
* @return {Object}
*/
render() {
return <div ref={ this.node } className={ this.props.className }></div>;
}
}

export default LeafletMap;