UNPKG

@open-tender/utils

Version:

A library of utils for use with Open Tender applications that utilize our cloud-based Order API.

317 lines (316 loc) 11.9 kB
import { MAX_DISTANCE } from './constants'; const RADIUS_MILES = 3959; // radius of the earth in miles const RADIUS_KM = 6371; // radius of the earth in kilometers // https://stackoverflow.com/questions/18883601/function-to-calculate-distance-between-two-coordinates export const getDistance = (pointA, pointB, inMiles = true) => { const { lat: lat1, lng: lng1 } = pointA; const { lat: lat2, lng: lng2 } = pointB; const R = inMiles ? RADIUS_MILES : RADIUS_KM; const dLat = deg2rad(lat2 - lat1); // see deg2rad below const dLng = deg2rad(lng2 - lng1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; return d; }; const deg2rad = (deg) => { return deg * (Math.PI / 180); }; // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html const pointInPolygon = (point, vs) => { const x = point[0], y = point[1]; let inside = false; for (let i = 0, j = vs.length - 1; i < vs.length; j = i++) { const xi = vs[i][0], yi = vs[i][1]; const xj = vs[j][0], yj = vs[j][1]; const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi; if (intersect) inside = !inside; } return inside; }; export const inZone = (latLng, polygon) => { const point = [latLng.lat, latLng.lng]; return pointInPolygon(point, polygon); }; export const addDistance = (revenueCenters, latLng) => { if (!latLng) return revenueCenters; const withDistance = revenueCenters.map(i => { const iLatLng = i.address.lat ? { lat: i.address.lat, lng: i.address.lng } : null; i.distance = iLatLng ? getDistance(latLng, iLatLng) : 1000; const { coordinates, priority } = i.delivery_zone; i.inZone = coordinates ? inZone(latLng, coordinates) : false; i.priority = priority !== null && priority !== void 0 ? priority : 0; return i; }); return withDistance; }; export const sortRevenueCenters = (revenueCenters, isDelivery = false) => { if (!isDelivery) { return [...revenueCenters].sort((a, b) => { var _a, _b; return ((_a = a.distance) !== null && _a !== void 0 ? _a : 0) - ((_b = b.distance) !== null && _b !== void 0 ? _b : 0); }); } const inZoneWithPriority = revenueCenters .filter(i => i.inZone && i.priority) .sort((a, b) => { var _a, _b; return ((_a = a.priority) !== null && _a !== void 0 ? _a : 0) - ((_b = b.priority) !== null && _b !== void 0 ? _b : 0); }); const inZoneWithoutPriority = revenueCenters .filter(i => i.inZone && !i.priority) .sort((a, b) => { var _a, _b; return ((_a = a.distance) !== null && _a !== void 0 ? _a : 0) - ((_b = b.distance) !== null && _b !== void 0 ? _b : 0); }); const outOfZone = revenueCenters .filter(i => !i.inZone) .sort((a, b) => { var _a, _b; return ((_a = a.distance) !== null && _a !== void 0 ? _a : 0) - ((_b = b.distance) !== null && _b !== void 0 ? _b : 0); }); return [...inZoneWithPriority, ...inZoneWithoutPriority, ...outOfZone]; }; export const calcMinDistance = (revenueCenters, maxDistance = MAX_DISTANCE) => { const withDistance = revenueCenters .filter(i => i.distance !== null && i.distance !== undefined) .map(i => { var _a; return (_a = i.distance) !== null && _a !== void 0 ? _a : 0; }); return withDistance ? Math.min(...withDistance) : maxDistance; }; export const checkServiceType = (revenueCenter, serviceType) => { const { service_types } = revenueCenter; if (!service_types) return false; return service_types.includes(serviceType); }; export const makePickupRevenueCenters = (revenueCenters, maxDistance = MAX_DISTANCE) => { const hasPickup = revenueCenters .filter(i => checkServiceType(i, 'PICKUP')) .filter(i => !i.distance || i.distance < maxDistance); return sortRevenueCenters(hasPickup); }; export const makeWalkinRevenueCenters = (revenueCenters, maxDistance = MAX_DISTANCE) => { const hasWalkin = revenueCenters .filter(i => checkServiceType(i, 'WALKIN')) .filter(i => !i.distance || i.distance < maxDistance); return sortRevenueCenters(hasWalkin); }; export const makeDeliveryRevenueCenters = (revenueCenters) => { const hasDelivery = revenueCenters.filter(i => checkServiceType(i, 'DELIVERY')); const sorted = sortRevenueCenters(hasDelivery, true); return sorted.filter(i => i.inZone); }; export const makePickupMesssaging = (address, latLng, count, minDistance, maxDistance = MAX_DISTANCE, messages = LOCATIONS_MESSAGES) => { if (address) { if (minDistance >= maxDistance) { return messages.PICKUP.addressFar; } else { return { title: `${count} ${messages.PICKUP.address.title}`, msg: messages.PICKUP.address.msg }; } } else if (latLng) { if (minDistance >= maxDistance) { return messages.PICKUP.geoFar; } else { return { title: `${count} ${messages.PICKUP.geo.title}`, msg: messages.PICKUP.geo.msg }; } } else { return messages.PICKUP.default; } }; export const makeWalkinMessaging = (address, latLng, count, minDistance, maxDistance = MAX_DISTANCE, messages = LOCATIONS_MESSAGES) => { const { title, msg } = makePickupMesssaging(address, latLng, count, minDistance, maxDistance, messages); return { title: title.replace('pickup', 'dine-in'), msg: msg.replace('pickup', 'dine-in') }; }; export const makeDeliveryMesssaging = (address, count, messages = LOCATIONS_MESSAGES) => { if (!address) { return messages.DELIVERY.default; } else if (!address.street) { return messages.DELIVERY.noStreet; } else { if (count) { const locationMsg = count > 1 ? 'locations deliver' : 'location delivers'; return { title: messages.DELIVERY.hasDelivery.title, msg: `${count} ${locationMsg} to your address.`, error: null }; } else { return messages.DELIVERY.noDelivery; } } }; export const makeDisplayedRevenueCenters = (revenueCenters, serviceType, address, latLng, maxDistance, isGroupOrder) => { const filtered = isGroupOrder ? revenueCenters.filter(i => i.group_ordering) : revenueCenters; if (serviceType === 'DELIVERY') { const displayed = makeDeliveryRevenueCenters(filtered); const count = displayed.length; const { title, msg, error } = makeDeliveryMesssaging(address, count); return { title, msg, error, displayed }; } else if (serviceType === 'WALKIN') { const displayed = makeWalkinRevenueCenters(filtered, maxDistance); const minDistance = calcMinDistance(displayed); const count = displayed.length; const { title, msg } = makeWalkinMessaging(address, latLng, count, minDistance, maxDistance); const error = null; return { title, msg, error, displayed }; } else { const displayed = makePickupRevenueCenters(filtered, maxDistance); const minDistance = calcMinDistance(displayed); const count = displayed.length; const { title, msg } = makePickupMesssaging(address, latLng, count, minDistance, maxDistance); const error = null; return { title, msg, error, displayed }; } }; export const LOCATIONS_MESSAGES = { PICKUP: { default: { title: 'Please choose a location', msg: 'Or enter a zip code to find the location nearest you.' }, address: { title: 'locations near you', msg: 'Please choose a location below.' }, addressFar: { title: "Looks like we don't have any locations that offer pickup in your area", msg: 'Sorry about that. Please enter a different address or head back and choose a different order type.' }, geo: { title: 'locations in your area', msg: 'Please enter an address or zip code for a more accurate result.' }, geoFar: { title: "Looks like we don't have any locations that offer pickup in your area", msg: 'Please enter an address or zip code if you live in a different area.' } }, DELIVERY: { default: { title: "Let's find the nearest location", msg: 'Please enter your address.', error: null }, noStreet: { title: 'Please enter a street address', msg: '', error: 'A full address with street number is required for delivery orders.' }, hasDelivery: { title: 'Delivery is available!', msg: 'Please choose a location below.', error: null }, noDelivery: { title: "Delivery isn't available in your area at this time", msg: "We're really sorry about that. Please enter a different address or head back and start a pickup order.", error: null } } }; export const renameLocation = (str, names) => { const [singular, plural] = names; return str .replace('1 locations', '1 location') .replace('locations', plural) .replace('location', singular) .replace(' a a', ' an a') .replace(' a e', ' an e') .replace(' a i', ' an i') .replace(' a o', ' an o') .replace(' a u', ' an u'); }; export const makeMapStyles = ({ labelColor, roadColor, featureColor, waterColor, backgroundColor }) => [ { featureType: 'transit', elementType: 'all', stylers: [{ visibility: 'off' }] }, { featureType: 'administrative', elementType: 'labels.text.fill', stylers: [{ visibility: 'off' }] }, { featureType: 'administrative', elementType: 'labels.text.stroke', stylers: [{ visibility: 'off' }] }, { featureType: 'road', elementType: 'labels.icon', stylers: [{ visibility: 'off' }] }, { featureType: 'road', elementType: 'geometry', stylers: [{ visibility: 'simplified' }, { color: roadColor }] }, { featureType: 'road', elementType: 'labels.text.fill', stylers: [{ color: labelColor }] }, { featureType: 'road', elementType: 'labels.text.stroke', stylers: [{ visibility: 'off' }] }, { featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: featureColor }] }, { featureType: 'road.highway', elementType: 'labels', stylers: [{ visibility: 'off' }] }, { featureType: 'water', elementType: 'geometry', stylers: [{ color: waterColor }] }, { featureType: 'water', elementType: 'labels', stylers: [{ visibility: 'off' }] }, { featureType: 'poi', elementType: 'geometry', stylers: [{ color: backgroundColor }] }, { featureType: 'poi', elementType: 'labels', stylers: [{ visibility: 'off' }] }, { featureType: 'landscape', elementType: 'geometry', stylers: [{ color: backgroundColor }] }, { featureType: 'landscape', elementType: 'labels', stylers: [{ visibility: 'off' }] } ];