Delivery cost calculator
vanilla.html
react.html
vue.html
common.css
common.ts
variables.ts
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../variables.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="./common.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
import {
LOCATION,
ROUTE_START,
OUT_OF_ZONES_PRICE,
ROUTE_STYLES,
TRANSLATIONS,
ZONES,
END_MARKER_COLOR,
START_MARKER_COLOR
} from '../variables';
import type {DomEventHandler, RouteFeature} from '@mappable-world/mappable-types';
import {calculatePrice, fetchRoute, type MapZone} from './common';
window.map = null;
interface InfoMessageProps {
text: string;
}
interface DeliverySumControlProps {
currentZone: MapZone;
outOfZoneLineLength: number;
price: number;
}
main();
async function main() {
// Waiting for all api elements to be loaded
await mappable.ready;
const {
MMap,
MMapDefaultSchemeLayer,
MMapDefaultFeaturesLayer,
MMapFeature,
MMapListener,
MMapControls,
MMapControl
} = mappable;
const {MMapDefaultMarker, MMapSearchControl} = await mappable.import('@mappable-world/mappable-default-ui-theme');
class InfoMessageClass extends mappable.MMapComplexEntity<InfoMessageProps> {
private _element!: HTMLDivElement;
private _detachDom!: () => void;
// Method for create a DOM control element
_createElement(props: InfoMessageProps) {
// Create a root element
const infoWindow = document.createElement('div');
infoWindow.classList.add('info-window');
infoWindow.innerHTML = props.text;
return infoWindow;
}
// Method for attaching the control to the map
_onAttach() {
this._element = this._createElement(this._props);
this._detachDom = mappable.useDomContext(this, this._element, this._element);
}
// Method for detaching control from the map
_onDetach() {
this._detachDom();
this._detachDom = undefined;
this._element = undefined;
}
}
class DeliveryCostControl extends mappable.MMapComplexEntity<{}> {
private _element!: HTMLDivElement;
private _detachDom!: () => void;
// Method for create a DOM control element
_createElement() {
// Create a root element
const windowElement = document.createElement('div');
windowElement.classList.add('delivery-cost-window');
const windowTitle = document.createElement('div');
windowTitle.classList.add('delivery-cost-title');
windowTitle.innerText = TRANSLATIONS.deliveryWindowTitle;
const windowContent = document.createElement('div');
windowContent.classList.add('delivery-cost-content');
for (const zone of ZONES) {
const zoneItem = document.createElement('div');
zoneItem.classList.add('delivery-item');
const colorBox = document.createElement('div');
colorBox.classList.add('delivery-item-colorbox');
colorBox.style.backgroundColor = zone.style.fill;
colorBox.style.borderColor = zone.style.stroke[0].color;
const text = document.createElement('div');
text.innerText = `${zone.name} — ${zone.price} ${TRANSLATIONS.currency}`;
zoneItem.appendChild(colorBox);
zoneItem.appendChild(text);
windowContent.appendChild(zoneItem);
}
const divider = document.createElement('hr');
divider.classList.add('divider');
const windowFooter = document.createElement('div');
windowFooter.classList.add('delivery-cost-footer');
windowFooter.innerText = `${TRANSLATIONS.deliveryWindowFooter} ${OUT_OF_ZONES_PRICE} ${TRANSLATIONS.currency}`;
windowElement.appendChild(windowTitle);
windowElement.appendChild(windowContent);
windowElement.appendChild(divider);
windowElement.appendChild(windowFooter);
return windowElement;
}
// Method for attaching the control to the map
_onAttach() {
this._element = this._createElement();
this._detachDom = mappable.useDomContext(this, this._element, this._element);
}
// Method for detaching control from the map
_onDetach() {
this._detachDom();
this._detachDom = undefined;
this._element = undefined;
}
}
class DeliverySumControl extends mappable.MMapComplexEntity<{}> {
private _element!: HTMLDivElement;
private _detachDom!: () => void;
private contentElement: HTMLDivElement;
private footerElement: HTMLDivElement;
// Method for create a DOM control element
_createElement() {
// Create a root element
const windowElement = document.createElement('div');
windowElement.classList.add('delivery-sum-window');
const windowTitle = document.createElement('div');
windowTitle.classList.add('delivery-sum-title');
windowTitle.innerText = TRANSLATIONS.deliverySumTitle;
const windowContent = document.createElement('div');
windowContent.classList.add('delivery-sum-content');
windowContent.id = 'delivery-sum-content';
windowElement.appendChild(windowTitle);
windowElement.appendChild(windowContent);
const windowFooter = document.createElement('div');
windowFooter.classList.add('delivery-sum-footer');
windowFooter.id = 'delivery-sum-footer';
windowElement.appendChild(windowFooter);
this.contentElement = windowContent;
this.footerElement = windowFooter;
return windowElement;
}
update(changedProps: Partial<DeliverySumControlProps>) {
this.contentElement.innerText = `${
!changedProps.outOfZoneLineLength && changedProps.currentZone ? `${changedProps.currentZone.name}` : ''
} ${changedProps.price.toFixed()} ${TRANSLATIONS.currency}`;
if (changedProps.outOfZoneLineLength) {
this.footerElement.classList.remove('hidden');
} else {
this.footerElement.classList.add('hidden');
}
this.footerElement.innerText = `${changedProps.currentZone.name} ${
!!changedProps.outOfZoneLineLength
? `+ ${changedProps.outOfZoneLineLength.toFixed()}${TRANSLATIONS.units}`
: ''
}`;
}
// Method for attaching the control to the map
_onAttach() {
this._element = this._createElement();
this._detachDom = mappable.useDomContext(this, this._element, this._element);
}
// Method for detaching control from the map
_onDetach() {
this._detachDom();
this._detachDom = undefined;
this._element = undefined;
}
}
// Initialize the map
map = new MMap(
// Pass the link to the HTMLElement of the container
document.getElementById('app'),
// Pass the map initialization parameters
{location: LOCATION, showScaleInCopyrights: true},
// Add a map scheme layer
[new MMapDefaultSchemeLayer({}), new MMapDefaultFeaturesLayer({})]
);
const rerenderComponents = ({price, outOfZoneLineLength, currentZone, coordinates, routeGeometry}) => {
if (!route.parent) {
map.addChild(route);
}
route.update({geometry: routeGeometry});
if (!marker.parent) {
map.addChild(marker);
}
marker.update({coordinates});
if (!deliverySumControl.parent) {
leftControl.addChild(deliverySumControl);
}
deliverySumControl.update({currentZone, price, outOfZoneLineLength});
};
/* A handler function that updates the route line
and shifts the map to the new route boundaries, if they are available. */
const routeHandler = (newRoute: RouteFeature) => {
const props = calculatePrice(newRoute);
return {
...props,
routeGeometry: newRoute.geometry
};
};
const searchHandler = (searchResults) => {
fetchRoute(ROUTE_START, searchResults[0].geometry.coordinates).then((route) => {
const renderProps = routeHandler(route);
rerenderComponents({...renderProps, coordinates: searchResults[0].geometry.coordinates});
});
};
const onMapClick: DomEventHandler = (object, event) => {
fetchRoute(ROUTE_START, event.coordinates).then((route) => {
const renderProps = routeHandler(route);
rerenderComponents({...renderProps, coordinates: event.coordinates});
});
};
const route = new MMapFeature({
geometry: {type: 'LineString', coordinates: []},
style: ROUTE_STYLES
});
const deliverySumControl = new DeliverySumControl({});
const marker = new MMapDefaultMarker({
coordinates: ROUTE_START,
iconName: 'building',
size: 'normal',
color: {day: END_MARKER_COLOR, night: END_MARKER_COLOR}
});
ZONES.forEach((zone) => map.addChild(new MMapFeature(zone)));
map.addChild(
new MMapDefaultMarker({
coordinates: ROUTE_START,
iconName: 'malls',
size: 'normal',
color: {day: START_MARKER_COLOR, night: START_MARKER_COLOR}
})
);
map.addChild(
new MMapControls({position: 'top right'}, [
new MMapSearchControl({
searchResult: searchHandler
})
])
);
const leftControl = new MMapControls({position: 'top left', orientation: 'vertical'}, [
new MMapControl({transparent: true}).addChild(new InfoMessageClass({text: TRANSLATIONS.tooltip})),
new MMapControl({transparent: true}).addChild(new DeliveryCostControl({}))
]);
map.addChild(leftControl);
map.addChild(new MMapListener({onClick: onMapClick}));
}
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
<link rel="stylesheet" href="./common.css" />
</head>
<body>
<div id="app"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"></script>
<!-- To make the map appear, you must add your apikey -->
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="react, typescript"
type="text/babel"
src="../variables.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="react, typescript"
type="text/babel"
src="./common.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
import {
LOCATION,
OUT_OF_ZONES_PRICE,
ROUTE_START,
ROUTE_STYLES,
TRANSLATIONS,
ZONES,
END_MARKER_COLOR,
START_MARKER_COLOR
} from '../variables';
import type {DomEventHandler, LngLat, RouteFeature, LineStringGeometry} from '@mappable-world/mappable-types';
import {calculatePrice, fetchRoute, type MapZone} from './common';
window.map = null;
main();
async function main() {
// For each object in the JS API, there is a React counterpart
// To use the React version of the API, include the module @mappable-world/mappable-reactify
const [mappableReact] = await Promise.all([
mappable.import('@mappable-world/mappable-reactify'),
mappable.ready
]);
const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
const {
MMap,
MMapDefaultSchemeLayer,
MMapDefaultFeaturesLayer,
MMapFeature,
MMapListener,
MMapControls,
MMapControl
} = reactify.module(mappable);
const {MMapDefaultMarker, MMapSearchControl} = reactify.module(
await mappable.import('@mappable-world/mappable-default-ui-theme')
);
const {useState, useCallback} = React;
function App() {
const [price, setPrice] = useState(null);
const [currentZone, setCurrentZone] = useState < MapZone > null;
const [outOfZoneLineLength, setOutOfZoneLineLength] = useState(null);
const [finishCoordinates, setFinishCoordinates] = useState < LngLat > null;
const [routeGeometry, setRouteGeometry] = useState < LineStringGeometry > null;
const onMapClick: DomEventHandler = useCallback((object, event) => {
setFinishCoordinates(event.coordinates);
fetchRoute(ROUTE_START, event.coordinates).then((route) => routeHandler(route));
}, []);
/* A handler function that updates the route line
and shifts the map to the new route boundaries, if they are available. */
const routeHandler = useCallback((newRoute: RouteFeature) => {
setRouteGeometry(newRoute.geometry);
const {outOfZoneLineLength, price, currentZone} = calculatePrice(newRoute);
setPrice(price);
setOutOfZoneLineLength(outOfZoneLineLength);
setCurrentZone(currentZone);
}, []);
const searchHandler = (searchResults) => {
setFinishCoordinates(searchResults[0].geometry.coordinates);
fetchRoute(ROUTE_START, searchResults[0].geometry.coordinates).then((route) => routeHandler(route));
};
return (
// Initialize the map and pass initialization parameters
<MMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
{/* Add a map scheme layer */}
<MMapDefaultSchemeLayer />
<MMapDefaultFeaturesLayer />
{ZONES.map((zone) => (
<MMapFeature key={zone.name} style={zone.style} geometry={zone.geometry} />
))}
{routeGeometry && <MMapFeature style={ROUTE_STYLES} geometry={routeGeometry} />}
<MMapDefaultMarker
color={{day: START_MARKER_COLOR, night: START_MARKER_COLOR}}
size="normal"
iconName="malls"
coordinates={ROUTE_START}
/>
{finishCoordinates && (
<MMapDefaultMarker
size="normal"
color={{day: END_MARKER_COLOR, night: END_MARKER_COLOR}}
iconName="building"
coordinates={finishCoordinates}
/>
)}
<MMapControls position="top right">
<MMapSearchControl searchResult={searchHandler} />
</MMapControls>
<MMapControls position="top left" orientation="vertical">
<MMapControl transparent>
<div className="info-window">{TRANSLATIONS.tooltip}</div>
</MMapControl>
<MMapControl transparent>
<div className="delivery-cost-window">
<div className="delivery-cost-title">{TRANSLATIONS.deliveryWindowTitle}</div>
<div className="delivery-cost-content">
{ZONES.map((zone) => (
<div key={zone.name} className="delivery-item">
<div
className="delivery-item-colorbox"
style={{
backgroundColor: zone.style.fill,
borderColor: zone.style.stroke[0].color
}}
/>
<div>
{zone.name} — {zone.price} {TRANSLATIONS.currency}
</div>
</div>
))}
</div>
<hr className="divider" />
<div className="delivery-cost-footer">
{TRANSLATIONS.deliveryWindowFooter} {OUT_OF_ZONES_PRICE} {TRANSLATIONS.currency}
</div>
</div>
</MMapControl>
{currentZone && price && (
<MMapControl transparent>
<div className="delivery-sum-window">
<div className="delivery-sum-title">{TRANSLATIONS.deliverySumTitle}</div>
<div className="delivery-sum-content">
{!outOfZoneLineLength && currentZone ? `${currentZone.name}` : ''} {price.toFixed()}
{TRANSLATIONS.currency}
</div>
{outOfZoneLineLength && (
<div className="delivery-sum-footer">
{currentZone.name} + {outOfZoneLineLength.toFixed()}
{TRANSLATIONS.units}
</div>
)}
</div>
</MMapControl>
)}
</MMapControls>
<MMapListener onClick={onMapClick} />
</MMap>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app')
);
}
</script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
<link rel="stylesheet" href="./common.css" />
</head>
<body>
<div id="app"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"></script>
<script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="../variables.ts"
></script>
<script
data-plugins="transform-modules-umd"
data-presets="typescript"
type="text/babel"
src="./common.ts"
></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel"></script>
<!-- prettier-ignore -->
<style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; } </style>
<link rel="stylesheet" href="./common.css" />
</head>
<body>
<div id="app"></div>
</body>
</html>
.info-window {
padding: 8px 12px 8px 40px;
border-radius: 12px;
background-color: #313133;
background-image: url('./info-icon.svg');
background-position: 10px 8px;
background-repeat: no-repeat;
color: #f2f5fa;
font-size: 14px;
line-height: 20px;
min-width: max-content;
}
.delivery-sum-window {
width: 220px;
padding: 10px 12px;
background-color: #212326;
border-radius: 12px;
box-sizing: border-box;
}
.delivery-sum-title {
color: #ffffff;
font-size: 14px;
font-weight: 400;
}
.delivery-sum-content {
margin-top: 8px;
font-weight: 500;
font-size: 16px;
color: #ffffff;
}
.delivery-sum-footer {
font-weight: 500;
font-size: 14px;
color: #f2f5fa;
opacity: 0.7;
}
.delivery-cost-window {
margin-top: 16px;
width: 220px;
padding: 8px;
background-color: #ffffff;
border-radius: 12px;
box-sizing: border-box;
box-shadow: 0px 4px 12px 0px #5f69831a;
}
.delivery-cost-title {
height: 40px;
padding: 8px;
font-weight: 500;
font-size: 16px;
box-sizing: border-box;
}
.delivery-cost-content {
padding: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.delivery-cost-footer {
box-sizing: border-box;
padding: 4px 8px 8px 8px;
height: 32px;
font-size: 14px;
}
.delivery-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.delivery-item-colorbox {
border-style: solid;
border-width: 3px;
box-sizing: border-box;
width: 20px;
height: 20px;
border-radius: 4px;
}
.divider {
border-top: 1px solid rgba(92, 94, 102, 0.14);
border-bottom: none;
border-radius: 8px;
margin: 8px;
}
.hidden {
display: none;
}
import type {
DrawingStyle,
LineStringGeometry,
LngLat,
PolygonGeometry,
RouteFeature
} from '@mappable-world/mappable-types';
import {Feature} from 'geojson';
import {OUT_OF_ZONES_PRICE, ZONES} from './variables';
export type MapZone = {
style: DrawingStyle;
geometry: PolygonGeometry;
price: number;
priority: number;
name: string;
};
// Wait for the api to load to access the map configuration
mappable.ready.then(() => {
mappable.import.registerCdn(
'https://cdn.jsdelivr.net/npm/{package}',
'@mappable-world/mappable-default-ui-theme@0.0'
);
});
export async function fetchRoute(startCoordinates: LngLat, endCoordinates: LngLat) {
// Request a route from the Router API with the specified parameters.
const routes = await mappable.route({
points: [startCoordinates, endCoordinates], // Start and end points of the route LngLat[]
type: 'driving', // Type of the route
bounds: true // Flag indicating whether to include route boundaries in the response
});
// Check if a route was found
if (!routes[0]) return;
// Convert the received route to a RouteFeature object.
const route = routes[0].toRoute();
// Check if a route has coordinates
if (route.geometry.coordinates.length == 0) return;
return route;
}
export function getLineStringLength(geometry: LineStringGeometry) {
const feature: Feature = {
type: 'Feature',
geometry,
properties: {}
};
return turf.length(feature);
}
export function getOutOfZoneLineSlice(route: LineStringGeometry, zone: PolygonGeometry) {
const splitPoints = turf.lineIntersect(zone, route);
const outOfZoneLineSlice = turf.lineSlice(
splitPoints.features[0].geometry.coordinates,
route.coordinates[route.coordinates.length - 1],
route
);
return outOfZoneLineSlice;
}
export function calculatePrice(route: RouteFeature) {
let price: number;
let outOfZoneLineLength: number;
const finalPoint = route.geometry.coordinates[route.geometry.coordinates.length - 1];
const sortedZones = ZONES.sort((a, b) => b.priority - a.priority);
let currentZone: MapZone = null;
for (const zone of sortedZones) {
const pointIsInZones = turf.booleanPointInPolygon(finalPoint, zone.geometry);
if (pointIsInZones) {
currentZone = zone;
}
}
if (currentZone) {
price = currentZone.price;
} else {
const lastZone = sortedZones[0];
const outOfZoneLineSlice = getOutOfZoneLineSlice(route.geometry, lastZone.geometry);
outOfZoneLineLength = getLineStringLength(outOfZoneLineSlice.geometry as LineStringGeometry);
price = lastZone.price + OUT_OF_ZONES_PRICE * outOfZoneLineLength;
currentZone = lastZone;
}
return {
price,
outOfZoneLineLength,
currentZone
};
}
import type {DrawingStyle, LngLat, MMapLocationRequest} from '@mappable-world/mappable-types';
import {MapZone} from './common';
export const LOCATION: MMapLocationRequest = {
center: [55.2742, 25.1975], // starting position [lng, lat]
zoom: 11.2 // starting zoom
};
export const ROUTE_START: LngLat = [55.2742, 25.1975];
export const END_MARKER_COLOR = '#313133';
export const START_MARKER_COLOR = '#2E4CE5';
export const ROUTE_STYLES: DrawingStyle = {
simplificationRate: 0,
stroke: [
{color: '#34D9AD', width: 6},
{color: '#000000', width: 8, opacity: 0.4}
],
fill: '#34D9AD'
};
export const ZONES: Array<MapZone> = [
{
style: {
simplificationRate: 0,
stroke: [{color: '#EF9A7A', width: 3}],
fill: 'rgba(239, 154, 122, 0.29)'
},
geometry: {
type: 'Polygon',
coordinates: [
[
[55.2402, 25.1875],
[55.2475, 25.1981],
[55.2592, 25.1911],
[55.2664, 25.1985],
[55.2776, 25.2155],
[55.2941, 25.2124],
[55.3005, 25.1988],
[55.2958, 25.1952],
[55.2923, 25.1904],
[55.287, 25.1836],
[55.2833, 25.1806],
[55.2786, 25.1791],
[55.2677, 25.1691],
[55.2402, 25.1875]
]
]
},
price: 40,
priority: 1,
name: 'zone A'
},
{
style: {
simplificationRate: 0,
stroke: [{color: '#EE5441', width: 3}],
fill: 'rgba(238, 84, 65, 0.1)'
},
geometry: {
type: 'Polygon',
coordinates: [
[
[55.19, 25.1524],
[55.2254, 25.1311],
[55.2202, 25.1239],
[55.2387, 25.1102],
[55.2526, 25.1234],
[55.2687, 25.1304],
[55.2909, 25.1317],
[55.317, 25.1384],
[55.3314, 25.1485],
[55.3361, 25.1536],
[55.3192, 25.1761],
[55.3133, 25.1831],
[55.3099, 25.1957],
[55.3091, 25.202],
[55.3176, 25.2079],
[55.3059, 25.2134],
[55.3005, 25.2186],
[55.294, 25.2245],
[55.2874, 25.2313],
[55.2763, 25.2367],
[55.2679, 25.2415],
[55.2619, 25.2482],
[55.2557, 25.2517],
[55.2526, 25.2452],
[55.2467, 25.2392],
[55.2555, 25.2323],
[55.2539, 25.2287],
[55.2488, 25.2321],
[55.2453, 25.2261],
[55.251, 25.221],
[55.2431, 25.2133],
[55.2338, 25.2247],
[55.2272, 25.2236],
[55.2228, 25.2176],
[55.2259, 25.2146],
[55.2307, 25.2161],
[55.2339, 25.2125],
[55.2304, 25.209],
[55.2268, 25.2019],
[55.19, 25.1524]
]
]
},
price: 80,
priority: 2,
name: 'zone B'
},
{
style: {
simplificationRate: 0,
stroke: [{color: '#7B72A5', width: 3}],
fill: 'rgba(123, 114, 165, 0.1)'
},
geometry: {
type: 'Polygon',
coordinates: [
[
[55.2648, 25.2848],
[55.2681, 25.2817],
[55.2683, 25.2756],
[55.2716, 25.2698],
[55.272, 25.2625],
[55.279, 25.2746],
[55.287, 25.2801],
[55.2919, 25.2827],
[55.2939, 25.2764],
[55.3082, 25.2824],
[55.3125, 25.2816],
[55.3268, 25.2924],
[55.3387, 25.3007],
[55.3541, 25.2913],
[55.3603, 25.2901],
[55.4097, 25.2636],
[55.4061, 25.2507],
[55.4046, 25.2375],
[55.4047, 25.2207],
[55.4033, 25.2127],
[55.3966, 25.1977],
[55.3965, 25.1983],
[55.3975, 25.1803],
[55.3789, 25.1337],
[55.3643, 25.1202],
[55.3479, 25.1124],
[55.3342, 25.1012],
[55.3212, 25.0914],
[55.3036, 25.075],
[55.2934, 25.0642],
[55.2832, 25.0602],
[55.2413, 25.0555],
[55.2247, 25.0534],
[55.2103, 25.0471],
[55.1977, 25.0487],
[55.1978, 25.0643],
[55.2149, 25.0833],
[55.2326, 25.1025],
[55.1882, 25.135],
[55.1638, 25.1548],
[55.2597, 25.2889],
[55.2648, 25.2848]
]
]
},
price: 150,
priority: 3,
name: 'zone C'
}
];
export const OUT_OF_ZONES_PRICE = 20;
export const TRANSLATIONS = {
deliveryWindowTitle: 'Delivery cost',
deliveryWindowFooter: 'Every extra 1 km + ',
deliverySumTitle: 'Your delivery',
units: 'km',
currency: 'AED',
tooltip: 'Pick address on map or by search'
};