Merge pull request #900 from sharetribe/reorganise-maps-config

Reorganize maps configuration
This commit is contained in:
Hannu Lyytikäinen 2018-08-24 14:22:29 +03:00 committed by GitHub
commit 8b70a918e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 258 additions and 222 deletions

View file

@ -35,3 +35,4 @@ REACT_APP_ENV=production
#
REACT_APP_AVAILABILITY_ENABLED=true
REACT_APP_DEFAULT_SEARCHES_ENABLED=true

View file

@ -19,6 +19,22 @@ way to update this template, but currently, we follow a pattern:
* [fix] Fix window resize redirecting to search page with reusable map component
[#905](https://github.com/sharetribe/flex-template-web/pull/905)
* [change] Maps configuration has been restructured. The new
configuration is agnostic of the maps provider in use and works with
both Google Maps as well as Mapbox.
The fuzzy location circle has less configuration, but otherwise all
the previous settings can be set also in the new configuration. See
`config.js` for details.
The default location searches are now enabled in the
`.env-template`. For old installations, the
`REACT_APP_DEFAULT_SEARCHES_ENABLED` env var should be set to
`true`. The default searches can then be configured in
`src/default-location-searches.js`.
[#900](https://github.com/sharetribe/flex-template-web/pull/900)
## v1.4.0 2018-08-17
* [change] Put availability calendar behind a feature flag
[#902](https://github.com/sharetribe/flex-template-web/pull/902)

View file

@ -23,6 +23,7 @@ them have defaults that work for development environment. For production deploys
| BASIC_AUTH_PASSWORD | Set to enable HTTP Basic Auth |
| REACT_APP_GOOGLE_ANALYTICS_ID | See: [Google Analytics](./analytics.md) |
| REACT_APP_AVAILABILITY_ENABLED | Enables availability calendar for listings. |
| REACT_APP_DEFAULT_SEARCHES_ENABLED | Enables default search suggestions in location autocomplete search input. |
## Defining configuration

View file

@ -1,39 +1,12 @@
import React from 'react';
// import { types as sdkTypes } from '../../util/sdkLoader';
import classNames from 'classnames';
import { getPlacePredictions, getPlaceDetails, locationBounds } from '../../util/googleMaps';
import { userLocation } from '../../util/maps';
import config from '../../config';
import css from './LocationAutocompleteInput.css';
// const { LatLng: SDKLatLng, LatLngBounds: SDKLatLngBounds } = sdkTypes;
export const CURRENT_LOCATION_ID = 'current-location';
const CURRENT_LOCATION_BOUNDS_DISTANCE = 1000; // meters
// A list of default predictions that can be shown when the user
// focuses on the autocomplete input without typing a search. This can
// be used to reduce typing and Geocoding API calls for common
// searches.
export const defaultPredictions = [
// Examples:
// Current user location from the browser geolocation API
// {
// id: CURRENT_LOCATION_ID,
// predictionPlace: {},
// },
// Helsinki
// {
// id: 'default-helsinki',
// predictionPlace: {
// address: 'Helsinki, Finland',
// origin: new SDKLatLng(60.16985, 24.93837),
// bounds: new SDKLatLngBounds(
// new SDKLatLng(60.29783, 25.25448),
// new SDKLatLng(59.92248, 24.78287)
// ),
// },
// },
];
// When displaying data from the Google Maps Places API, and
// attribution is required next to the results.
@ -117,7 +90,7 @@ class GeocoderGoogleMaps {
return {
address: '',
origin: latlng,
bounds: locationBounds(latlng, CURRENT_LOCATION_BOUNDS_DISTANCE),
bounds: locationBounds(latlng, config.maps.search.currentLocationBoundsDistance),
};
});
}

View file

@ -5,7 +5,6 @@ import config from '../../config';
const { LatLng: SDKLatLng, LatLngBounds: SDKLatLngBounds } = sdkTypes;
export const CURRENT_LOCATION_ID = 'current-location';
const CURRENT_LOCATION_BOUNDS_DISTANCE = 1000; // meters
const locationBounds = (latlng, distance) => {
const bounds = new window.mapboxgl.LngLat(latlng.lng, latlng.lat).toBounds(distance);
@ -34,31 +33,6 @@ const placeBounds = prediction => {
return null;
};
// A list of default predictions that can be shown when the user
// focuses on the autocomplete input without typing a search. This can
// be used to reduce typing and Geocoding API calls for common
// searches.
export const defaultPredictions = [
// Examples:
// Current user location from the browser geolocation API
// {
// id: CURRENT_LOCATION_ID,
// predictionPlace: {},
// },
// Helsinki
// {
// id: 'default-helsinki',
// predictionPlace: {
// address: 'Helsinki, Finland',
// origin: new SDKLatLng(60.16985, 24.93837),
// bounds: new SDKLatLngBounds(
// new SDKLatLng(60.29783, 25.25448),
// new SDKLatLng(59.92248, 24.78287)
// ),
// },
// },
];
export const GeocoderAttribution = () => null;
/**
@ -140,7 +114,7 @@ class GeocoderMapbox {
return {
address: '',
origin: latlng,
bounds: locationBounds(latlng, CURRENT_LOCATION_BOUNDS_DISTANCE),
bounds: locationBounds(latlng, config.maps.search.currentLocationBoundsDistance),
};
});
}

View file

@ -7,15 +7,21 @@ import { IconSpinner } from '../../components';
import { propTypes } from '../../util/types';
import IconHourGlass from './IconHourGlass';
import IconCurrentLocation from './IconCurrentLocation';
import Geocoder, {
GeocoderAttribution,
defaultPredictions,
CURRENT_LOCATION_ID,
} from './GeocoderGoogleMaps';
// import Geocoder, { GeocoderAttribution, defaultPredictions, CURRENT_LOCATION_ID } from './GeocoderMapbox';
import Geocoder, { GeocoderAttribution, CURRENT_LOCATION_ID } from './GeocoderGoogleMaps';
// import Geocoder, { GeocoderAttribution, CURRENT_LOCATION_ID } from './GeocoderMapbox';
import config from '../../config';
import css from './LocationAutocompleteInput.css';
// A list of default predictions that can be shown when the user
// focuses on the autocomplete input without typing a search. This can
// be used to reduce typing and Geocoding API calls for common
// searches.
export const defaultPredictions = (config.maps.search.suggestCurrentLocation
? [{ id: CURRENT_LOCATION_ID, predictionPlace: {} }]
: []
).concat(config.maps.search.defaults);
const DEBOUNCE_WAIT_TIME = 200;
const KEY_CODE_ARROW_UP = 38;
const KEY_CODE_ARROW_DOWN = 40;

View file

@ -8,13 +8,13 @@ import config from '../../config';
* It handles some of the google map initialization states.
*/
const DynamicGoogleMap = withGoogleMap(props => {
const { center, zoom, address, coordinatesConfig } = props;
const { center, zoom, address, mapsConfig } = props;
const { markerURI, anchorX, anchorY, width, height } = coordinatesConfig.customMarker || {};
const markerIcon = coordinatesConfig.customMarker
const { enabled, url, anchorX, anchorY, width, height } = mapsConfig.customMarker;
const markerIcon = enabled
? {
icon: {
url: markerURI,
url,
// The origin for this image is (0, 0).
origin: new window.google.maps.Point(0, 0),
@ -27,8 +27,15 @@ const DynamicGoogleMap = withGoogleMap(props => {
const marker = <Marker position={center} {...markerIcon} title={address} />;
const circleProps = {
options: coordinatesConfig.circleOptions,
radius: coordinatesConfig.coordinateOffset,
options: {
fillColor: mapsConfig.fuzzy.circleColor,
fillOpacity: 0.2,
strokeColor: mapsConfig.fuzzy.circleColor,
strokeOpacity: 0.5,
strokeWeight: 1,
clickable: false,
},
radius: mapsConfig.fuzzy.offset,
center,
};
@ -59,7 +66,7 @@ const DynamicGoogleMap = withGoogleMap(props => {
},
}}
>
{coordinatesConfig.fuzzy ? circle : marker}
{mapsConfig.fuzzy.enabled ? circle : marker}
</GoogleMap>
);
});
@ -67,8 +74,8 @@ const DynamicGoogleMap = withGoogleMap(props => {
DynamicGoogleMap.defaultProps = {
address: '',
center: null,
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
coordinatesConfig: config.coordinates,
zoom: config.maps.fuzzy.enabled ? config.maps.fuzzy.defaultZoomLevel : 11,
mapsConfig: config.maps,
};
DynamicGoogleMap.propTypes = {
@ -78,7 +85,7 @@ DynamicGoogleMap.propTypes = {
lng: number.isRequired,
}).isRequired,
zoom: number,
coordinatesConfig: object,
mapsConfig: object,
};
export default DynamicGoogleMap;

View file

@ -4,25 +4,21 @@ import uniqueId from 'lodash/uniqueId';
import { circlePolyline } from '../../util/maps';
import config from '../../config';
const mapMarker = coordinatesConfig => {
const { customMarker } = coordinatesConfig;
if (customMarker) {
const mapMarker = mapsConfig => {
const { enabled, url, width, height } = mapsConfig.customMarker;
if (enabled) {
const element = document.createElement('div');
element.style.backgroundImage = `url(${customMarker.markerURI})`;
element.style.width = `${customMarker.width}px`;
element.style.height = `${customMarker.height}px`;
element.style.backgroundImage = `url(${url})`;
element.style.width = `${width}px`;
element.style.height = `${height}px`;
return new window.mapboxgl.Marker({ element });
} else {
return new window.mapboxgl.Marker();
}
};
const circleLayer = (center, coordinatesConfig, layerId) => {
const { fillColor, fillOpacity } = coordinatesConfig.circleOptions;
const path = circlePolyline(center, coordinatesConfig.coordinateOffset).map(([lat, lng]) => [
lng,
lat,
]);
const circleLayer = (center, mapsConfig, layerId) => {
const path = circlePolyline(center, mapsConfig.fuzzy.offset).map(([lat, lng]) => [lng, lat]);
return {
id: layerId,
type: 'fill',
@ -37,8 +33,8 @@ const circleLayer = (center, coordinatesConfig, layerId) => {
},
},
paint: {
'fill-color': fillColor,
'fill-opacity': fillOpacity,
'fill-color': mapsConfig.fuzzy.circleColor,
'fill-opacity': 0.2,
},
};
};
@ -59,7 +55,7 @@ class DynamicMapboxMap extends Component {
this.updateFuzzyCirclelayer = this.updateFuzzyCirclelayer.bind(this);
}
componentDidMount() {
const { center, zoom, coordinatesConfig } = this.props;
const { center, zoom, mapsConfig } = this.props;
const position = [center.lng, center.lat];
this.map = new window.mapboxgl.Map({
@ -71,12 +67,12 @@ class DynamicMapboxMap extends Component {
});
this.map.addControl(new window.mapboxgl.NavigationControl(), 'top-left');
if (coordinatesConfig.fuzzy) {
if (mapsConfig.fuzzy.enabled) {
this.map.on('load', () => {
this.map.addLayer(circleLayer(center, coordinatesConfig, this.fuzzyLayerId));
this.map.addLayer(circleLayer(center, mapsConfig, this.fuzzyLayerId));
});
} else {
this.centerMarker = mapMarker(coordinatesConfig);
this.centerMarker = mapMarker(mapsConfig);
this.centerMarker.setLngLat(position).addTo(this.map);
}
}
@ -92,7 +88,7 @@ class DynamicMapboxMap extends Component {
return;
}
const { center, zoom, coordinatesConfig } = this.props;
const { center, zoom, mapsConfig } = this.props;
const { lat, lng } = center;
const position = [lng, lat];
@ -110,7 +106,7 @@ class DynamicMapboxMap extends Component {
}
// fuzzy circle change
if (coordinatesConfig.fuzzy && centerChanged) {
if (mapsConfig.fuzzy.enabled && centerChanged) {
if (this.map.loaded()) {
this.updateFuzzyCirclelayer();
} else {
@ -118,14 +114,14 @@ class DynamicMapboxMap extends Component {
}
}
// NOTE: coordinatesConfig changes are not handled
// NOTE: mapsConfig changes are not handled
}
updateFuzzyCirclelayer() {
if (!this.map) {
// map already removed
return;
}
const { center, coordinatesConfig } = this.props;
const { center, mapsConfig } = this.props;
const { lat, lng } = center;
const position = [lng, lat];
@ -133,7 +129,7 @@ class DynamicMapboxMap extends Component {
// We have to use a different layer id to avoid Mapbox errors
this.fuzzyLayerId = generateFuzzyLayerId();
this.map.addLayer(circleLayer(center, coordinatesConfig, this.fuzzyLayerId));
this.map.addLayer(circleLayer(center, mapsConfig, this.fuzzyLayerId));
this.map.setCenter(position);
}
@ -150,8 +146,8 @@ class DynamicMapboxMap extends Component {
DynamicMapboxMap.defaultProps = {
address: '',
center: null,
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
coordinatesConfig: config.coordinates,
zoom: config.maps.fuzzy.enabled ? config.maps.fuzzy.defaultZoomLevel : 11,
mapsConfig: config.maps,
};
DynamicMapboxMap.propTypes = {
@ -161,7 +157,7 @@ DynamicMapboxMap.propTypes = {
lng: number.isRequired,
}).isRequired,
zoom: number,
coordinatesConfig: object,
mapsConfig: object,
};
export default DynamicMapboxMap;

View file

@ -31,9 +31,12 @@ export const WithObfuscatedLocation = {
obfuscatedCenter: new LatLng(60.16502999999999, 24.940064399999983),
address: 'Sharetribe',
zoom: 14,
coordinatesConfig: {
...config.coordinates,
fuzzy: true,
mapsConfig: {
...config.maps,
fuzzy: {
...config.maps.fuzzy,
enabled: true,
},
},
},
};
@ -48,9 +51,12 @@ export const WithCircleLocation = {
center: new LatLng(60.16502999999999, 24.940064399999983),
obfuscatedCenter: obfuscatedCoordinates(new LatLng(60.16502999999999, 24.940064399999983)),
address: 'Sharetribe',
coordinatesConfig: {
...config.coordinates,
fuzzy: true,
mapsConfig: {
...config.maps,
fuzzy: {
...config.maps.fuzzy,
enabled: true,
},
},
},
};

View file

@ -18,32 +18,27 @@ export class Map extends Component {
center,
obfuscatedCenter,
zoom,
coordinatesConfig,
mapsConfig,
useStaticMap,
} = this.props;
const classes = classNames(rootClassName || css.root, className);
const mapClasses = mapRootClassName || css.mapRoot;
if (coordinatesConfig.fuzzy && !obfuscatedCenter) {
if (mapsConfig.fuzzy.enabled && !obfuscatedCenter) {
throw new Error(
'Map: obfuscatedCenter prop is required when config.coordinates.fuzzy === true'
'Map: obfuscatedCenter prop is required when config.maps.fuzzy.enabled === true'
);
}
if (!coordinatesConfig.fuzzy && !center) {
throw new Error('Map: center prop is required when config.coordinates.fuzzy === false');
if (!mapsConfig.fuzzy.enabled && !center) {
throw new Error('Map: center prop is required when config.maps.fuzzy.enabled === false');
}
const location = coordinatesConfig.fuzzy ? obfuscatedCenter : center;
const location = mapsConfig.fuzzy.enabled ? obfuscatedCenter : center;
return !isMapsLibLoaded() ? (
<div className={classes} />
) : useStaticMap ? (
<StaticMap
center={location}
zoom={zoom}
address={address}
coordinatesConfig={coordinatesConfig}
/>
<StaticMap center={location} zoom={zoom} address={address} mapsConfig={mapsConfig} />
) : (
<DynamicMap
containerElement={<div className={classes} />}
@ -53,7 +48,7 @@ export class Map extends Component {
center={location}
zoom={zoom}
address={address}
coordinatesConfig={coordinatesConfig}
mapsConfig={mapsConfig}
/>
);
}
@ -64,8 +59,8 @@ Map.defaultProps = {
rootClassName: null,
mapRootClassName: null,
address: '',
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
coordinatesConfig: config.coordinates,
zoom: config.maps.fuzzy.enabled ? config.maps.fuzzy.defaultZoomLevel : 11,
mapsConfig: config.maps,
useStaticMap: false,
};
@ -77,7 +72,7 @@ Map.propTypes = {
center: propTypes.latlng,
obfuscatedCenter: propTypes.latlng,
zoom: number,
coordinatesConfig: object,
mapsConfig: object,
useStaticMap: bool,
};

View file

@ -34,21 +34,17 @@ const convertOpacity = opacity => {
};
// Draw a circle polyline for fuzzy location.
const drawFuzzyCircle = (coordinatesConfig, center) => {
if (
!(
coordinatesConfig &&
typeof coordinatesConfig === 'object' &&
center &&
typeof center === 'object'
)
) {
const drawFuzzyCircle = (mapsConfig, center) => {
if (!(mapsConfig && typeof mapsConfig === 'object' && center && typeof center === 'object')) {
return '';
}
const { fillColor, fillOpacity, strokeColor, strokeWeight } = coordinatesConfig.circleOptions;
const fillColor = mapsConfig.fuzzy.circleColor;
const fillOpacity = 0.2;
const strokeColor = mapsConfig.fuzzy.circleColor;
const strokeWeight = 1;
const circleRadius = coordinatesConfig.coordinateOffset || 500;
const circleRadius = mapsConfig.fuzzy.offset || 500;
const circleStrokeWeight = strokeWeight || 1;
const circleStrokeColor = formatColorFromString(strokeColor);
const circleStrokeOpacity = convertOpacity(DEFAULT_STROKE_OPACITY);
@ -70,8 +66,8 @@ const drawFuzzyCircle = (coordinatesConfig, center) => {
// Get custom marker data for static map URI
const customMarker = (options, lat, lng) => {
const { anchorX, anchorY, markerURI } = options;
return [`anchor:${anchorX},${anchorY}`, `icon:${markerURI}`, `${lat},${lng}`].join('|');
const { anchorX, anchorY, url } = options;
return [`anchor:${anchorX},${anchorY}`, `icon:${url}`, `${lat},${lng}`].join('|');
};
class StaticGoogleMap extends Component {
@ -84,7 +80,7 @@ class StaticGoogleMap extends Component {
}
render() {
const { center, zoom, address, coordinatesConfig, dimensions } = this.props;
const { center, zoom, address, mapsConfig, dimensions } = this.props;
const { lat, lng } = center || {};
const { width, height } = dimensions;
@ -92,10 +88,10 @@ class StaticGoogleMap extends Component {
// 1. if fuzzy coordinates are used, return circle path
// 2. if customMarker is defined in config.js, use that
// 3. else return default marker
const targetMaybe = coordinatesConfig.fuzzy
? { path: drawFuzzyCircle(coordinatesConfig, center) }
: coordinatesConfig.customMarker
? { markers: customMarker(coordinatesConfig.customMarker, lat, lng) }
const targetMaybe = mapsConfig.fuzzy.enabled
? { path: drawFuzzyCircle(mapsConfig, center) }
: mapsConfig.customMarker.enabled
? { markers: customMarker(mapsConfig.customMarker, lat, lng) }
: { markers: `${lat},${lng}` };
const srcParams = stringify({
@ -103,7 +99,7 @@ class StaticGoogleMap extends Component {
zoom,
size: `${width}x${height}`,
maptype: 'roadmap',
key: config.googleMapsAPIKey,
key: config.maps.googleMapsAPIKey,
...targetMaybe,
});
@ -118,8 +114,8 @@ StaticGoogleMap.defaultProps = {
rootClassName: null,
address: '',
center: null,
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
coordinatesConfig: config.coordinates,
zoom: config.maps.fuzzy.enabled ? config.maps.fuzzy.defaultZoomLevel : 11,
mapsConfig: config.maps,
};
StaticGoogleMap.propTypes = {
@ -131,7 +127,7 @@ StaticGoogleMap.propTypes = {
lng: number.isRequired,
}).isRequired,
zoom: number,
coordinatesConfig: object,
mapsConfig: object,
// from withDimensions
dimensions: shape({

View file

@ -9,42 +9,41 @@ const formatColor = color => {
return color.replace(/^#/, '');
};
const fuzzyCircleOverlay = (center, coordinatesConfig) => {
const {
strokeWeight,
strokeColor,
strokeOpacity,
fillColor,
fillOpacity,
} = coordinatesConfig.circleOptions;
const path = circlePolyline(center, coordinatesConfig.coordinateOffset);
const fuzzyCircleOverlay = (center, mapsConfig) => {
const strokeWeight = 1;
const strokeColor = mapsConfig.fuzzy.circleColor;
const strokeOpacity = 0.5;
const fillColor = mapsConfig.fuzzy.circleColor;
const fillOpacity = 0.2;
const path = circlePolyline(center, mapsConfig.fuzzy.offset);
const styles = `-${strokeWeight}+${formatColor(strokeColor)}-${strokeOpacity}+${formatColor(
fillColor
)}-${fillOpacity}`;
return `path${styles}(${encodeURIComponent(polyline.encode(path))})`;
};
const customMarkerOverlay = (center, coordinatesConfig) => {
const { markerURI } = coordinatesConfig.customMarker;
return `url-${encodeURIComponent(markerURI)}(${center.lng},${center.lat})`;
const customMarkerOverlay = (center, mapsConfig) => {
const { url } = mapsConfig.customMarker;
return `url-${encodeURIComponent(url)}(${center.lng},${center.lat})`;
};
const markerOverlay = center => {
return `pin-s(${center.lng},${center.lat})`;
};
const mapOverlay = (center, coordinatesConfig) => {
if (coordinatesConfig.fuzzy) {
return fuzzyCircleOverlay(center, coordinatesConfig);
const mapOverlay = (center, mapsConfig) => {
if (mapsConfig.fuzzy.enabled) {
return fuzzyCircleOverlay(center, mapsConfig);
}
if (coordinatesConfig.customMarker) {
return customMarkerOverlay(center, coordinatesConfig);
if (mapsConfig.customMarker.enabled) {
return customMarkerOverlay(center, mapsConfig);
}
return markerOverlay(center);
};
const StaticMapboxMap = props => {
const { address, center, zoom, coordinatesConfig, dimensions } = props;
const { address, center, zoom, mapsConfig, dimensions } = props;
const { width, height } = dimensions;
const libLoaded = typeof window !== 'undefined' && window.mapboxgl;
@ -52,13 +51,13 @@ const StaticMapboxMap = props => {
return null;
}
const overlay = mapOverlay(center, coordinatesConfig);
const overlay = mapOverlay(center, mapsConfig);
const src =
'https://api.mapbox.com/styles/v1/mapbox/streets-v10/static' +
(overlay ? `/${overlay}` : '') +
`/${center.lng},${center.lat},${zoom}` +
`/${width}x${height}` +
`?access_token=${window.mapboxgl.accessToken}`;
`?access_token=${config.maps.mapboxAccessToken}`;
return <img src={src} alt={address} />;
};
@ -66,8 +65,8 @@ const StaticMapboxMap = props => {
StaticMapboxMap.defaultProps = {
address: '',
center: null,
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
coordinatesConfig: config.coordinates,
zoom: config.maps.fuzzy.enabled ? config.maps.fuzzy.defaultZoomLevel : 11,
mapsConfig: config.maps,
};
StaticMapboxMap.propTypes = {
@ -77,7 +76,7 @@ StaticMapboxMap.propTypes = {
lng: number.isRequired,
}).isRequired,
zoom: number,
coordinatesConfig: object,
mapsConfig: object,
// from withDimensions
dimensions: shape({

View file

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import { arrayOf, bool, func, number, string, shape } from 'prop-types';
import { arrayOf, bool, func, number, string, shape, object } from 'prop-types';
import { withRouter } from 'react-router-dom';
import { withGoogleMap, GoogleMap } from 'react-google-maps';
import classNames from 'classnames';
@ -279,14 +279,14 @@ export class SearchMapComponent extends Component {
onCloseAsModal,
onIdle,
zoom,
coordinatesConfig,
mapsConfig,
activeListingId,
} = this.props;
const classes = classNames(rootClassName || css.root, className);
const mapClasses = mapRootClassName || css.mapRoot;
const listingsWithLocation = originalListings.filter(l => !!l.attributes.geolocation);
const listings = coordinatesConfig.fuzzy
const listings = mapsConfig.fuzzy.enabled
? withCoordinatesObfuscated(listingsWithLocation)
: listingsWithLocation;
const infoCardOpen = this.state.infoCardOpen;
@ -345,7 +345,7 @@ SearchMapComponent.defaultProps = {
onCloseAsModal: null,
useLocationSearchBounds: true,
zoom: 11,
coordinatesConfig: config.coordinates,
mapsConfig: config.maps,
};
SearchMapComponent.propTypes = {
@ -361,9 +361,7 @@ SearchMapComponent.propTypes = {
onIdle: func.isRequired,
useLocationSearchBounds: bool, // eslint-disable-line react/no-unused-prop-types
zoom: number,
coordinatesConfig: shape({
fuzzy: bool.isRequired,
}),
mapsConfig: object,
// from withRouter
history: shape({

View file

@ -1,4 +1,5 @@
import * as custom from './marketplace-custom-config.js';
import defaultLocationSearches from './default-location-searches';
const env = process.env.REACT_APP_ENV;
const dev = process.env.REACT_APP_ENV === 'development';
@ -262,57 +263,75 @@ const siteFacebookPage = 'https://www.facebook.com/Sharetribe/';
// You should create one to track social sharing in Facebook
const facebookAppId = null;
// Google Maps API key is needed for static map images.
const googleMapsAPIKey = process.env.REACT_APP_GOOGLE_MAPS_API_KEY;
const maps = {
mapboxAccessToken: process.env.REACT_APP_MAPBOX_ACCESS_TOKEN,
googleMapsAPIKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
const coordinates = {
// If true, obfuscate the coordinates of the listings that are shown
// on a map.
fuzzy: false,
// The location search input can be configured to show default
// searches when the user focuses on the input and hasn't yet typed
// anything. This reduces typing and avoids too many Geolocation API
// calls for common searches.
search: {
// When enabled, the first suggestion is "Current location" that
// uses the browser Geolocation API to query the user's current
// location.
suggestCurrentLocation: process.env.REACT_APP_DEFAULT_SEARCHES_ENABLED === 'true',
// Default zoom level when showing a single circle on a Map. Should
// be small enough so the whole circle fits in.
fuzzyDefaultZoomLevel: 14,
// Distance in meters for calculating the bounding box around the
// current location.
currentLocationBoundsDistance: 1000,
// When fuzzy === true, the coordinates on the Map component are
// obfuscated and a circle is shown instead of a marker. To decide a
// proper value for the offset and the radius, see:
//
// https://gis.stackexchange.com/a/8674
// Amount of maximum offset in meters that is applied to obfuscate
// the original coordinates. The actual value is random, but the
// obfuscated coordinates are withing a circle that has the same
// radius as the offset.
coordinateOffset: 500,
// Options to style the circle appearance.
//
// See: https://developers.google.com/maps/documentation/javascript/reference#CircleOptions
circleOptions: {
fillColor: '#c0392b',
fillOpacity: 0.2,
strokeColor: '#c0392b',
strokeOpacity: 0.5,
strokeWeight: 1,
clickable: false,
// Example location can be edited in the
// `default-location-searches.js` file.
defaults:
process.env.REACT_APP_DEFAULT_SEARCHES_ENABLED === 'true' ? defaultLocationSearches : [],
},
// An option to use custom marker on static maps. Uncomment to enable it.
// https://developers.google.com/maps/documentation/maps-static/dev-guide#Markers
// When fuzzy locations are enabled, coordinates on maps are
// obfuscated randomly around the actual location.
//
// Note 1: fuzzy coordinate circle overwrites these custom marker settings)
// Note 2: markerURI needs to be a public URI accessible by Google Maps API servers.
// The easiest place is /public/static/icons/ folder, but then the marker image is not available
// while developing through localhost.
// NOTE: This only hides the locations in the UI level, the actual
// coordinates are still accessible in the HTTP requests and the
// Redux store.
fuzzy: {
enabled: false,
// customMarker: {
// markerURI: encodeURI(`${canonicalRootURL}/static/icons/map-marker-32x32.png`),
// anchorX: 16,
// anchorY: 32,
// width: 32,
// height: 32,
// },
// Amount of maximum offset in meters that is applied to obfuscate
// the original coordinates. The actual value is random, but the
// obfuscated coordinates are withing a circle that has the same
// radius as the offset.
offset: 500,
// Default zoom level when showing a single circle on a Map. Should
// be small enough so the whole circle fits in.
defaultZoomLevel: 13,
// Color of the circle on the Map component.
circleColor: '#c0392b',
},
// Custom marker image to use in the Map component.
//
// NOTE: Not used if fuzzy locations are enabled.
customMarker: {
enabled: false,
// Publicly accessible URL for the custom marker image.
//
// The easiest place is /public/static/icons/ folder, but then the
// marker image is not available while developing through
// localhost.
url: encodeURI(`${canonicalRootURL}/static/icons/map-marker-32x32.png`),
// Dimensions of the marker image.
width: 32,
height: 32,
// Position to anchor the image in relation to the coordinates,
// ignored when using Mapbox.
anchorX: 16,
anchorY: 32,
},
};
// NOTE: only expose configuration that should be visible in the
@ -350,8 +369,7 @@ const config = {
facebookAppId,
sentryDsn,
usingSSL,
googleMapsAPIKey,
coordinates,
maps,
custom,
};

View file

@ -25,7 +25,7 @@ class SectionMapMaybe extends Component {
const address = publicData.location ? publicData.location.address : '';
const classes = classNames(rootClassName || css.sectionMap, className);
const mapProps = config.coordinates.fuzzy
const mapProps = config.maps.fuzzy.enabled
? { obfuscatedCenter: obfuscatedCoordinates(geolocation, listingId ? listingId.uuid : null) }
: { address, center: geolocation };
const map = <Map {...mapProps} useStaticMap={this.state.isStatic} />;

View file

@ -0,0 +1,51 @@
import { types as sdkTypes } from './util/sdkLoader';
const { LatLng, LatLngBounds } = sdkTypes;
// An array of locations to show in the LocationAutocompleteInput when
// the input is in focus but the user hasn't typed in any search yet.
//
// Each item in the array should be an object with a unique `id` (String) and a
// `predictionPlace` (util.types.place) properties.
export default [
{
id: 'default-helsinki',
predictionPlace: {
address: 'Helsinki, Finland',
origin: new LatLng(60.16985, 24.93837),
bounds: new LatLngBounds(new LatLng(60.29783, 25.25448), new LatLng(59.92248, 24.78287)),
},
},
{
id: 'default-turku',
predictionPlace: {
address: 'Turku, Finland',
origin: new LatLng(60.45181, 22.26663),
bounds: new LatLngBounds(new LatLng(60.53045, 22.38197), new LatLng(60.33361, 22.06644)),
},
},
{
id: 'default-tampere',
predictionPlace: {
address: 'Tampere, Finland',
origin: new LatLng(61.49775, 23.76095),
bounds: new LatLngBounds(new LatLng(61.83657, 24.11838), new LatLng(61.42728, 23.5422)),
},
},
{
id: 'default-oulu',
predictionPlace: {
address: 'Oulu, Finland',
origin: new LatLng(65.01208, 25.46507),
bounds: new LatLngBounds(new LatLng(65.56434, 26.77069), new LatLng(64.8443, 24.11494)),
},
},
{
id: 'default-ruka',
predictionPlace: {
address: 'Ruka, Finland',
origin: new LatLng(66.16546, 29.15172),
bounds: new LatLngBounds(new LatLng(66.16997, 29.16773), new LatLng(66.16095, 29.13572)),
},
},
];

View file

@ -12,10 +12,9 @@ const { LatLng } = sdkTypes;
*/
const obfuscatedCoordinatesImpl = (latlng, cacheKey) => {
const { lat, lng } = latlng;
const offset = config.coordinates.coordinateOffset;
// https://gis.stackexchange.com/questions/25877/generating-random-locations-nearby
const r = offset / 111300;
const r = config.maps.fuzzy.offset / 111300;
const y0 = lat;
const x0 = lng;