Improve coordinate obfuscation

Now the obfuscation results are memoized so that the results stay
stable for each listing id.
This commit is contained in:
Kimmo Puputti 2018-02-01 17:01:39 +02:00
parent b8b8b94375
commit 505cafdb7a
9 changed files with 84 additions and 29 deletions

View file

@ -13,6 +13,7 @@ export const WithMarker = {
),
props: {
center: new LatLng(60.16502999999999, 24.940064399999983),
obfuscatedCenter: new LatLng(60.16502999999999, 24.940064399999983),
address: 'Sharetribe',
zoom: 22,
},
@ -26,6 +27,7 @@ export const WithObfuscatedLocation = {
),
props: {
center: new LatLng(60.16502999999999, 24.940064399999983),
obfuscatedCenter: new LatLng(60.16502999999999, 24.940064399999983),
address: 'Sharetribe',
zoom: 14,
coordinatesConfig: {

View file

@ -3,7 +3,6 @@ import { string, number, object } from 'prop-types';
import { withGoogleMap, GoogleMap, Marker, Circle } from 'react-google-maps';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import { obfuscatedCoordinates } from '../../util/maps';
import config from '../../config';
import CustomMarker from './images/marker-32x32.png';
@ -70,12 +69,23 @@ export class Map extends Component {
mapRootClassName,
address,
center,
obfuscatedCenter,
zoom,
coordinatesConfig,
} = this.props;
const classes = classNames(rootClassName || css.root, className);
const mapClasses = mapRootClassName || css.mapRoot;
const location = coordinatesConfig.fuzzy ? obfuscatedCoordinates(center) : center;
if (coordinatesConfig.fuzzy && !obfuscatedCenter) {
throw new Error(
'Map: obfuscatedCenter prop is required when config.coordinates.fuzzy === true'
);
}
if (!coordinatesConfig.fuzzy && !center) {
throw new Error('Map: center prop is required when config.coordinates.fuzzy === false');
}
const location = coordinatesConfig.fuzzy ? obfuscatedCenter : center;
const centerLocationForGoogleMap = { lat: location.lat, lng: location.lng };
return (
@ -95,6 +105,7 @@ Map.defaultProps = {
className: null,
rootClassName: null,
mapRootClassName: null,
address: '',
zoom: config.coordinates.fuzzy ? config.coordinates.fuzzyDefaultZoomLevel : 11,
coordinatesConfig: config.coordinates,
};
@ -103,8 +114,9 @@ Map.propTypes = {
className: string,
rootClassName: string,
mapRootClassName: string,
address: string.isRequired,
center: propTypes.latlng.isRequired,
address: string,
center: propTypes.latlng,
obfuscatedCenter: propTypes.latlng,
zoom: number,
coordinatesConfig: object,
};

View file

@ -71,14 +71,15 @@ const reducedToArray = mapListings => {
const withCoordinatesObfuscated = listings => {
return listings.map(listing => {
const { attributes, ...rest } = listing;
const attrs = {
...attributes,
geolocation: obfuscatedCoordinates(attributes.geolocation),
};
const { id, attributes, ...rest } = listing;
const geolocation = obfuscatedCoordinates(attributes.geolocation, id ? id.uuid : null);
return {
id,
...rest,
attributes: attrs,
attributes: {
...attributes,
geolocation,
},
};
});
};

View file

@ -241,7 +241,7 @@ const facebookAppId = null;
const coordinates = {
// If true, obfuscate the coordinates of the listings that are shown
// on a map.
fuzzy: process.env.REACT_APP_FUZZY_COORDINATES || true,
fuzzy: process.env.REACT_APP_FUZZY_COORDINATES || false,
fuzzyDefaultZoomLevel: 14,

View file

@ -575,7 +575,11 @@ export class ListingPageComponent extends Component {
</div>
<SectionRulesMaybe publicData={publicData} />
<SectionMapMaybe geolocation={geolocation} publicData={publicData} />
<SectionMapMaybe
geolocation={geolocation}
publicData={publicData}
listingId={currentListing.id}
/>
<div className={css.reviewsContainer}>
<h2 className={css.reviewsHeading}>

View file

@ -1,36 +1,52 @@
import React from 'react';
import { object, string } from 'prop-types';
import { string } from 'prop-types';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import { obfuscatedCoordinates } from '../../util/maps';
import { Map } from '../../components';
import config from '../../config';
import css from './ListingPage.css';
const SectionMapMaybe = props => {
const { className, rootClassName, geolocation, publicData } = props;
const { className, rootClassName, geolocation, publicData, listingId } = props;
if (!geolocation) {
return null;
}
const address = publicData.location ? publicData.location.address : '';
const classes = classNames(rootClassName || css.locationContainer, className);
return geolocation ? (
const mapProps = config.coordinates.fuzzy
? { obfuscatedCenter: obfuscatedCoordinates(geolocation, listingId ? listingId.uuid : null) }
: { address, center: geolocation };
return (
<div className={classes}>
<h2 className={css.locationTitle}>
<FormattedMessage id="ListingPage.locationTitle" />
</h2>
<div className={css.map}>
<Map center={geolocation} address={address} />
<Map {...mapProps} />
</div>
</div>
) : null;
);
};
SectionMapMaybe.defaultProps = { className: null, rootClassName: null };
SectionMapMaybe.defaultProps = {
rootClassName: null,
className: null,
geolocation: null,
listingId: null,
};
SectionMapMaybe.propTypes = {
className: string,
rootClassName: string,
geolocation: propTypes.latlng.isRequired,
publicData: object.isRequired,
className: string,
geolocation: propTypes.latlng,
listingId: propTypes.uuid,
};
export default SectionMapMaybe;

View file

@ -334,6 +334,11 @@ exports[`ListingPage matches snapshot 1`] = `
"lng": 60,
}
}
listingId={
UUID {
"uuid": "listing1",
}
}
publicData={Object {}}
rootClassName={null}
/>

View file

@ -124,7 +124,7 @@ exports[`SearchPageComponent matches snapshot 1`] = `
"strokeWeight": 0.5,
},
"circleRadius": 500,
"fuzzy": true,
"fuzzy": false,
"fuzzyDefaultZoomLevel": 14,
}
}

View file

@ -1,19 +1,34 @@
import { random } from 'lodash';
import { random, memoize, round } from 'lodash';
import { types as sdkTypes } from './sdkLoader';
const { LatLng } = sdkTypes;
const obfuscatedCoordinatesImpl = latlng => {
const { lat, lng } = latlng;
const threshold = 0.01;
const newLat = round(lat + random(-1 * threshold, threshold), 5);
const newLng = round(lng + random(-1 * threshold, threshold), 5);
return new LatLng(newLat, newLng);
};
const obfuscationKeyGetter = (latlng, cacheKey) => cacheKey;
const memoizedObfuscatedCoordinatesImpl = memoize(obfuscatedCoordinatesImpl, obfuscationKeyGetter);
/**
* Make the given coordinates randomly a little bit different.
*
* @param {LatLng} latlng coordinates
* @param {String?} cacheKey if given, the results are memoized and
* the same coordinates are returned for the same key as long as the
* cache isn't cleared (e.g. with page refresh). This results in
* e.g. same listings always getting the same obfuscated coordinates
* if the listing id is used as the cache key.
*
* @return {LatLng} obfuscated coordinates
*/
export const obfuscatedCoordinates = latlng => {
const { lat, lng } = latlng;
const threshold = 0.01;
const newLat = lat + random(-1 * threshold, threshold);
const newLng = lng + random(-1 * threshold, threshold);
return new LatLng(newLat, newLng);
export const obfuscatedCoordinates = (latlng, cacheKey = null) => {
return cacheKey
? memoizedObfuscatedCoordinatesImpl(latlng, cacheKey)
: obfuscatedCoordinatesImpl(latlng);
};