Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

HTML Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

            
              <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Get Service Area | ThinkGeo</title>

</head>

<body>
    <div id="map">
        <!-- Set up the sidebar control panel. -->
        <div class="travelPanel collapsed">
            <button id="toggle">>></button>
            <h5>Driving Start Location</h5>
            <input type="text" id="startPoint" placeholder="latitude, logitude" data-origin="" />
            <h5>Service Area Options</h5>
            <div class="travel-row" id="serivce-area">
                <button class="active" value="Time">Time <br /> (minutes)</button>
                <button value="Distance">Distance <br />(kilometers)</button>
            </div>
            <div class="travel-row">
                <span>Driving Direction</span>
                <select id="drive-direction">
                    <option value="From" selected>Away from</option>
                    <option value="To">Towards to</option>
                </select>
            </div>
            <div class="travel-row">
                <span>Grid Size (Meter)</span>
                <select id="grid-size">
                    <option value="300">300</option>
                    <option value="500" selected>500</option>
                    <option value="700">700</option>
                    <option value="1000">1000</option>
                </select>
            </div>
            <div class="travel-row" id="drive-time-limit">
                <span>Driving Minute Limits (min)</span>
                <ul>
                    <li><input type="number" value="10" min="1" max="19" /></li>
                    <li><input type="number" value="20" min="11" max="29" /></li>
                    <li><input type="number" value="30" min="21" max="59" /></li>
                    <li><input type="number" value="60" min="31" max="60" /></li>
                </ul>
            </div>
            <div class="travel-row hide" id="drive-distance-limit">
                <span>Driving Distance Limits (km)</span>
                <ul>
                    <li><input type="number" value="15" min="1" max="29" /></li>
                    <li><input type="number" value="30" min="15" max="44" /></li>
                    <li><input type="number" value="45" min="30" max="59" /></li>
                    <li><input type="number" value="60" min="45" max="100" /></li>
                </ul>
            </div>
            <div class="travel-row">
                <span>Service Area Type</span>
                <select id="area-type">
                    <option value="Polygon" selected>Polygon</option>
                    <option value="Linestring">Linestring</option>
                </select>
            </div>
            <div class="travel-row" id="contour-granularity">
                <span>Contour Granularity (0-1)</span><br />
                <input type="range" min="0" max="1" value="1" step="0.1">
            </div>
            <div class="travel-row center">
                <button id="refresh">Refresh</button>
            </div>
        </div>

        <!-- Set up the loading animation. -->
        <div class="loading hide">
            <div></div>
            <div></div>
            <div></div>
        </div>

        <!-- Set up error message tip for tile loading error. -->
        <div id="error-message">
            <p></p>
        </div>

        <!-- Set up context menu when right click the map. -->
        <div id="ol-contextmenu" class="hide">
            <ul>
                <li id="add-start-point">
                    Set Start Location
                </li>
                <li id="clear">
                    Clear
                </li>
            </ul>
        </div>
    </div>

    <!-- Set up instruction tip. -->
    <div id="instruction-tip">
        <p>Set your start point by dragging the vehicle icon or right-clicking on the map to see what area you drive to
            within a specified minutes.</p>
    </div>
</body>

</html>
            
          
!
            
              body {
  margin: 0;
  position: relative;
  font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.hide {
  display: none !important;
}

h5 {
  margin-top: 0.1rem;
  margin-bottom: 0.1rem;
  color: white;
}

.travelPanel {
  position: absolute;
  left: 1em;
  top: 1em;
  width: 260px;
  z-index: 4;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.62);
  border-color: #666 !important;
  border-radius: 3px;
  background-color: rgba(0, 0, 0, .75) !important;
  padding: 10px;
  color: white;
}

.travelPanel input[type='text'] {
  font-size: 12px;
  width: 220px;
  border: 0;
  background-color: transparent;
  height: 30px;
  line-height: 30px;
  color: rgb(213, 213, 213);
  padding: 0 40px 0px 0px;
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
  border-bottom: 1px solid #ccc;
  margin-bottom: 1rem;
}

.travelPanel input:focus {
  outline: none;
}

#toggle {
  display: none;
}

.travel-row {
  margin-top: 10px;
  display: inline-block;
  width: 100%;
  padding-bottom: 5px;
  border-bottom: solid 1px rgba(109, 108, 108, 0.314) !important;
}

.travel-row.center {
  text-align: center;
}

.travel-row ul {
  list-style: none;
  margin: 0;
  padding: 0.5em 0;
}

.travel-row li {
  background-color: white;
  border-color: #d8d8d8;
  border-left-style: solid;
  border-width: 1px;
  color: #333;
  cursor: pointer;
  display: inline-block;
  font-size: 12px;
  float: left;
  padding: 0.4rem 0.5rem;
  width: 42%;
  text-align: center;
  position: relative;
  -webkit-transition: 0.1s ease-in-out;
  transition: 0.1s ease-in-out;
}

.travel-row button {
  border-color: #d8d8d8;
  border-left-style: solid;
  border-width: 1px;
  color: #333;
  cursor: pointer;
  background-color: #fff;
  font-size: 12px;
  float: left;
  padding: 0.4rem 0.5rem;
  width: 48%;
}

.travel-row button:focus {
  outline: none;
}

.travel-row .active {
  position: relative;
  background-color: rgb(213, 213, 213);
  z-index: 1;
}

.travel-row li {
  width: 18%;
}

.travel-row span,
label,
input[type=select] {
  font-size: 0.83rem;
  color: #eee;
  display: inline-block;
  margin-bottom: 5px;
}

.travel-row input[type=range] {
  width: 98%;
}

.travel-row select {
  float: right;
  margin-right: 10px;
  width: 90px;
}

input[type=range] {
  line-height: 1.42857;
  color: #555;
  background-color: #fff;
  -webkit-appearance: none;
  border-radius: 10px;
}

input[type=range]::-webkit-slider-runnable-track {
  height: 12px;
  border-radius: 10px;
  box-shadow: 0 0 1px #def3f8, inset 0 0 .125em #0d1112;
}

input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none;
  margin-top: 1px;
  height: 10px;
  width: 10px;
  background: #808080;
  border-radius: 50%;
  border: solid 0.125em rgba(53, 53, 53, 0.5);
  box-shadow: 0 .125em .125em #3b4547;
}

#drive-time-limit input,
#drive-distance-limit input {
  width: 35px;
  border: 0;
}

#refresh {
  font-weight: bold;
  margin-bottom: 5px;
  margin-left: 50%;
}

#map {
  width: 100%;
  height: 100%;
  position: relative;
}

.ol-zoom {
  bottom: .5em;
  right: 0.5em;
  top: unset;
  left: unset;
}

.ol-zoom button {
  width: 1.2em !important;
  height: 1.2em !important;
}

#instruction-tip {
  width: max-content;
  position: fixed;
  bottom: 10px;
  margin-left: 50%;
  transform: translateX(-50%);
  -ms-transform: translateX(-50%);
  -webkit-transform: translateX(-50%);
  -moz-transform: translateX(-50%);
  -o-transform: translateX(-50%);
  color: #fff;
}

#instruction-tip p {
  background-color: rgba(58, 58, 58, 0.85);
  border-radius: 3px;
  padding: 10px 20px;
}

#instruction-tip.gone {
  bottom: -100px;
  opacity: 0;
  transition: bottom 500ms ease-out, opacity 300ms ease-out;
}

.loading {
  position: absolute;
  top: 48%;
  left: calc(50% - 45px);
  height: 20px;
  z-index: 9;
}

.loading div {
  display: inline-block;
  border-radius: 50%;
  height: 20px;
  width: 20px;
  background-color: #ccc;
  margin-left: 10px;
}

.loading div:nth-child(1) {
  animation: loading 1s ease-in-out infinite;
}

.loading div:nth-child(2) {
  animation: loading 1s ease-in-out infinite;
  animation-delay: -0.8s;
}

.loading div:nth-child(3) {
  animation: loading 1s ease-in-out infinite;
  animation-delay: -1.6s;
}

@keyframes loading {
  0% {
    transform: scale(0);
  }

  50% {
    transform: scale(1);
  }

  100% {
    transform: scale(0);
  }
}

#error-message {
  position: absolute;
  top: -150px;
  left: 50%;
  width: auto;
  min-width: 300px;
  margin-left: auto;
  text-align: center;
  transform: translate(-50%, 0);
  transition: top 0.6s;
  z-index: 5;
}

#error-message.show {
  top: 15px;
  transition: top 0.6s;
}

#error-message p {
  line-height: 40px;
  padding-left: 10px;
  padding-right: 10px;
  border-radius: 3px;
  border-color: #f5c6cb;
  background-color: #f8d7da;
  color: #721c24;
}

#ol-contextmenu {
  width: 165px;
  position: absolute;
  z-index: 999;
  top: 30px;
  left: 300px;
  background-color: white;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  padding: 5px 0;
  border-radius: 2px;
  border: 1px solid #cccccc;
}

#ol-contextmenu.hide {
  display: none;
}

#ol-contextmenu ul {
  margin: 0;
  padding: 0;
}

#ol-contextmenu li {
  list-style: none;
  padding: 5px 0 5px 10px;
}

#ol-contextmenu li:nth-child(1) {
  border-bottom: 1px solid #e1e1e1;
}

#ol-contextmenu li:hover {
  cursor: pointer;
  background-color: #e1e1e1;
}

@media (max-width: 767px) {
  .travelPanel {
    left: 1px;
    transition: left 500ms ease-out;
    padding: 5px;
    border-radius: 0;
    top: 1px;
  }

  .travelPanel.collapsed{
    transition: left 500ms ease-out;
  }

  .travelPanel.collapsed {
    left: -271px;
  }

  .travel-row{
    margin-top: 0;
    padding-bottom: 0;
  }

  .travel-row button{
    padding: 0.1rem 0.2rem;
  }

  .travel-row li{
    padding: 0.1rem 0.4rem;
  }

  .travelPanel input[type='text']{
    margin-bottom: 0.2rem;
  }

  #toggle {
    display: inline-block;
    position: absolute;
    left: 100%;
    transform: rotate(180deg);
    height: 3em;
    width: 2em;
    padding: 0;
    top: -1px;
    background-color: rgba(0, 0, 0, .75);
    color: #fff;
    border: 1px solid #eee;
    cursor: pointer;
  }

  #toggle:focus{
    outline: 0;
  }

  .travelPanel.collapsed #toggle {
    transform: rotate(0deg);
  }

  #instruction-tip{
    width: 80%;
  }
}
            
          
!
            
              /*===========================================================================*/
// Get Service Area
// Sample map by ThinkGeo
// 
//   1. ThinkGeo Cloud API Key
//   2. Map Control Setup
//   3. ThinkGeo Map Icon Fonts
//   4. Routing Setup
//   5. UI control setup
//   6. Derive the Custom Class Drag
//   7. Event Listeners
/*===========================================================================*/


/*---------------------------------------------*/
// 1. ThinkGeo Cloud API Key
/*---------------------------------------------*/

// First, let's define our ThinkGeo Cloud API key, which we'll use to
// authenticate our requests to the ThinkGeo Cloud API.  Each API key can be
// restricted for use only from a given web domain or IP address.  To create your
// own API key, you'll need to sign up for a ThinkGeo Cloud account at
// https://cloud.thinkgeo.com.
const apiKey = 'WPLmkj3P39OPectosnM1jRgDixwlti71l8KYxyfP2P0~';


/*---------------------------------------------*/
// 2. Map Control Setup
/*---------------------------------------------*/

// Here's where we set up our map.  We're going to create layers, styles, 
// and define our initial view when the page first loads.

// Now we'll create the base layer for our map.  The base layer uses the ThinkGeo
// Cloud Maps Vector Tile service to display a detailed street map.  For more
// info, see our wiki:
// https://wiki.thinkgeo.com/wiki/thinkgeo_cloud_maps_vector_tiles
const darkLayer = new ol.mapsuite.VectorTileLayer('https://cdn.thinkgeo.com/worldstreets-styles/3.0.0/dark.json', {
    apiKey: apiKey,
    layerName: 'dark'
});

// Create a default view for the map when it starts up.
let startInputCoord = [-71.06734129275898, 42.35059783495578];
const view = new ol.View({
    // Center the map on Boston and start at zoom level 8.
    center: ol.proj.fromLonLat(startInputCoord),
    maxResolution: 40075016.68557849 / 512,
    progressiveZoom: true,
    zoom: 8,
    minZoom: 2,
    maxZoom: 19
});

// This function will create and initialize our interactive map.
// We'll call it later when our POI icon font has been fully downloaded,
// which ensures that the POI icons display as intended.
let map;
let app = {};
let vectorSource;
const initializeMap = () => {
    map = new ol.Map({
        renderer: 'webgl',
        loadTilesWhileAnimating: true,
        loadTilesWhileInteracting: true,
        // Add our previously-defined ThinkGeo Cloud Vector Tile layer to the map.
        layers: [darkLayer],
        // States that the HTML tag with id="map" should serve as the container for our map.
        target: 'map',
        view: view,
        // Add an interaction to map that allows drag point icon.
        interactions: ol.interaction.defaults().extend([new app.Drag()])
    });

    addRoutingPoint();
    performRouting();
    mobileCompatibility();
    addEventListenerToMap(map);
};

const addEventListenerToMap = (map) => {
    // Add a "pointermove" listener to map which is when the pointer is moving over the start, end and mid point, the cursor should be "pointer" appearance.
    map.on('pointermove', function (e) {
        if (e.dragging) {
            return;
        }
        const pixel = map.getEventPixel(e.originalEvent);
        const options = {
            // Only find feature on the routing layer not the base vector tile layer.
            layerFilter: function (layer) {
                if (layer instanceof ol.layer.VectorTile) {
                    return false;
                }
                return true;
            }
        };
        const hit = map.hasFeatureAtPixel(pixel, options);
        let cursor = false;
        if (hit) {
            const features = map.getFeaturesAtPixel(pixel, options);
            features.some((feature) => {
                if (feature.get('name') === 'start') {
                    cursor = true;
                    return true;
                }
            });
        } else {
            cursor = false;
        }
        map.getTargetElement().style.cursor = cursor ? 'pointer' : '';
    });
}

// In this custom object, we're going to define eight styles:
//   1. The appearance of the start point icon.
//   2. The appearance of the snap point icon.
//   3. The appearance of the service line.
//   4. The appearance of the service area.
const styles = {
    point: new ol.style.Style({
        image: new ol.style.Icon({
            anchor: [0.5, 0.5],
            anchorXUnits: 'fraction',
            anchorYUnits: 'fraction',
            opacity: 1,
            crossOrigin: "Anonymous",
            src: 'https://samples.thinkgeo.com/cloud/example/image/vehicle.png',
            imgSize: [30, 30]
        }),
        zIndex: 2
    }),
    snap: new ol.style.Style({
        image: new ol.style.Circle({
            radius: 8,
            stroke: new ol.style.Stroke({
                color: '#000000ff',
                width: 2
            }),
            fill: new ol.style.Fill({
                color: '#fffc24cc'
            })
        })
    }),
    line: [
        new ol.style.Style({
            stroke: new ol.style.Stroke({
                width: 3,
                color: 'rgba(40, 132, 176, 0.7)'
            })
        }),
        new ol.style.Style({
            stroke: new ol.style.Stroke({
                width: 3,
                color: 'rgba(26, 182, 64, 0.7)'
            })
        }),
        new ol.style.Style({
            stroke: new ol.style.Stroke({
                width: 3,
                color: 'rgba(163, 163, 49, 0.7)'
            })
        }),
        new ol.style.Style({
            stroke: new ol.style.Stroke({
                width: 3,
                color: 'rgba(157, 81, 37, 0.7)'
            })
        }),
    ],
    polygon: [
        new ol.style.Style({
            fill: new ol.style.Fill({
                color: 'rgba(40, 132, 176, 0.7)'
            })
        }),
        new ol.style.Style({
            fill: new ol.style.Fill({
                color: 'rgba(26, 182, 64, 0.7)'
            })
        }),
        new ol.style.Style({
            fill: new ol.style.Fill({
                color: 'rgba(163, 163, 49, 0.7)'
            })
        }),
        new ol.style.Style({
            fill: new ol.style.Fill({
                color: 'rgba(157, 81, 37, 0.7)'
            })
        })
    ]
}

// Do some compatibility on mible and IOS client.
const mobileCompatibility = () => {
    let left, top;
    let clientWidth = document.documentElement.clientWidth;
    let clientHeight = document.documentElement.clientHeight;
    const contextmenu = document.querySelector('#ol-contextmenu');
    const contextWidth = 165;

    // Add an event lister which will shows when we right clic on the map.
    map.getViewport().addEventListener('contextmenu', function (e) {
        hideOrShowContextMenu('show');
        let point = map.getEventCoordinate(e);
        left =
            e.clientX + contextWidth > clientWidth ? clientWidth - contextWidth : e.clientX;
        top =
            e.clientY + contextmenu.offsetHeight > clientHeight ?
            clientHeight - contextmenu.offsetHeight :
            e.clientY;

        contextmenu.style.left = left + 'px';
        contextmenu.style.top = top + 'px';
        startInputCoord = new ol.proj.transform(point, 'EPSG:3857', 'EPSG:4326');
    })
}

// Create the routing layer and add it to map.
let pointFeature;
const addRoutingPoint = () => {
    vectorSource = new ol.source.Vector();
    routingLayer = new ol.layer.Vector({
        source: vectorSource
    });
    routingLayer.set('layerName', 'routing');
    map.addLayer(routingLayer);
    pointFeature = new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat(startInputCoord)),
        name: 'start'
    });
    pointFeature.setStyle(styles.point);
    vectorSource.addFeature(pointFeature)
}

/*---------------------------------------------*/
// 3. ThinkGeo Map Icon Fonts
/*---------------------------------------------*/

// Finally, we'll load the Map Icon Fonts using ThinkGeo's WebFont loader. 
// The loaded Icon Fonts will be used to render POI icons on top of the map's 
// background layer.  We'll initalize the map only once the font has been 
// downloaded.  For more info, see our wiki: 
// https://wiki.thinkgeo.com/wiki/thinkgeo_iconfonts 
WebFont.load({
    custom: {
        families: ["vectormap-icons"],
        urls: ["https://cdn.thinkgeo.com/vectormap-icons/2.0.0/vectormap-icons.css"],
        testStrings: {
            'vectormap-icons': '\ue001'
        }
    },
    // The "active" property defines a function to call when the font has
    // finished downloading.  Here, we'll call our initializeMap method.
    active: initializeMap
});



/*---------------------------------------------*/
// 4. Routing Setup
/*---------------------------------------------*/

// At this point we'll built up the methods and functionality that will  
// actually perform the routing using the ThinkGeo Cloud and then 
// display the results on the map.

// We use thinkgeocloudclient.js, which is an open-source Javascript SDK for making 
// request to ThinkGeo Cloud Service. It simplifies the process of the code of request.

// We need to create the instance of Routing client and authenticate the API key.
const routingClient = new tg.RoutingClient(apiKey);


// This method performs the actual routing using the ThinkGeo Cloud. 
// By passing the coordinates of the map location and some other options, we can 
// get back the service area as we send the request.  For more details, see our wiki:
// https://wiki.thinkgeo.com/wiki/thinkgeo_cloud_routing
let count = 0;
let timer;
const errorMessage = document.querySelector('#error-message');
const performRouting = () => {
    clear();
    // Show the loading animation.
    document.querySelector('.loading').classList.remove('hide');
    errorMessage.classList.remove('show');

    // Hide the drag tips.
    if (count === 0 || count === 1) {
        count++;
    } else if (count === 2) {
        const tipClass = document.querySelector('#instruction-tip').classList;
        tipClass.contains('hide') ? null : tipClass.add('hide');
    }

    // Get the params from panel and pass them to routingClient to perform our routing request.
    const serviceLimitsType = document.querySelector('#serivce-area .active').value;
    const travelDirection = document.querySelector('#drive-direction').value;
    const gridSizeInMeters = document.querySelector('#grid-size').value;
    const minuteLimit = [];
    const timeLimitInput = document.querySelectorAll('#drive-time-limit input');
    timeLimitInput.forEach(item => {
        minuteLimit.push(Number(item.value))
    })
    const distanceLimit = [];
    const distanceLimitInput = document.querySelectorAll('#drive-distance-limit input');
    distanceLimitInput.forEach(item => {
        distanceLimit.push(Number(item.value))
    })
    const timeLimit = document.querySelector('#drive-time-limit');
    const serviceLimits = timeLimit.classList.contains('hide') ? distanceLimit.join(',') : minuteLimit.join(',');
    const serviceAreaType = document.querySelector('#area-type').value;
    const contourGranularity = document.querySelector('#contour-granularity input').value;
    const distanceUnit = 'Kilometer';

    const options = {
        contourGranularity: contourGranularity,
        gridSizeInMeters: gridSizeInMeters,
        serviceAreaType: serviceAreaType,
        serviceLimits: serviceLimits,
        serviceLimitsType: serviceLimitsType,
        travelDirection: travelDirection,
        distanceUnit: distanceUnit
    };
    const callback = (status, response) => {
        if (timer !== undefined && timer !== null) {
            clearTimeout(timer);
        }
        document.querySelector('.loading').classList.add('hide');
        let message;
        if (status === 200) {
            drawAreas(response.data, serviceAreaType);
        } else if (status === 410 || status === 401 || status === 400) {
            message = response.error ? response.error.message : (Object.keys(response.data).map(key => {
                return response.data[key];
            }) || "Request failed");
        } else {
            message = 'Request failed.';
        }

        if (message) {
            errorMessage.querySelector('p').innerHTML = `${status}: ${message}`;
            errorMessage.classList.add('show');
            timer = setTimeout(() => {
                errorMessage.classList.remove('show');
            }, 5000)
        }
    }
    routingClient.getServiceArea(startInputCoord[1], startInputCoord[0], serviceLimits, callback, options);
}

// Draw the service area when we get the route result from server.
const drawAreas = (res, type) => {
    const areas = res.serviceAreas;
    const coordinate = res.waypoint.coordinate;
    const snapPoint = [coordinate.x, coordinate.y];

    // Add different service areas.
    for (let i = 0, l = areas.length; i < l; i++) {
        const wkt = areas[i];
        const format = new ol.format.WKT();
        const feature = format.readFeature(wkt, {
            dataProjection: 'EPSG:4326',
            featureProjection: 'EPSG:3857'
        });
        feature.set('name', 'areas');
        const style = type === 'Linestring' ? styles.line[i] : styles.polygon[i];
        feature.setStyle(style);
        vectorSource.addFeature(feature);
    }

    // Add sanp point.
    const snapFeature = new ol.Feature({
        geometry: new ol.geom.Point(ol.proj.fromLonLat(snapPoint)),
        name: 'snap'
    });
    snapFeature.setStyle(styles.snap);
    vectorSource.addFeature(snapFeature);
}

// Remove the previous areas.
const clear = () => {
    const features = vectorSource.getFeatures();
    features.forEach(feature => {
        if (feature.get('name') === 'areas' || feature.get('name') === 'snap' || feature.get('name') === 'line') {
            vectorSource.removeFeature(feature);
        }
    })
}


/*---------------------------------------------*/
// 5. UI control setup
/*---------------------------------------------*/

// At this step, we create some methods to control the sample UI.

// This method controls the context menu to show or hide. When we 
// right click on the map, the context menu shows up, while we click 
// anywhere, the context menu will hide. 
const hideOrShowContextMenu = (visible) => {
    let contextmenu = document.querySelector('#ol-contextmenu');
    switch (visible) {
        case 'hide':
            contextmenu.classList.add('hide');
            break;
        case 'show':
            contextmenu.classList.remove('hide');
    }
};

// When we drag the start point, the coordinates of the start point 
// has been changed, so we have to update this coordinates to the left 
// sidebar input box.
let startInputEle;
const updateInputValue = (noFixed) => {
    const coord_ = startInputCoord.slice();
    startInputEle.setAttribute('data-origin', startInputCoord[1] + ', ' + startInputCoord[0]);
    if (noFixed) {
        startInputEle.value = coord_[1] + ', ' + coord_[0];
    } else {
        startInputEle.value = coord_[1].toFixed(6) + ', ' + coord_[0].toFixed(6);
    }
}


/*---------------------------------------------*/
// 6. Derive the Custom Class Drag
/*---------------------------------------------*/

// Since we need to drag the point to change the destination or start location, 
// we have to make the point draggable. At this step, we derived the custom class Drag.
app.Drag = function () {
    ol.interaction.Pointer.call(this, {
        handleDownEvent: app.Drag.prototype.handleDownEvent,
        handleDragEvent: app.Drag.prototype.handleDragEvent,
        handleUpEvent: app.Drag.prototype.handleUpEvent
    });
    this.coordinate_ = null;
    this.feature_ = null;
};
ol.inherits(app.Drag, ol.interaction.Pointer);

// Function handling "down" events.
// If the function returns true then a drag sequence is started.
app.Drag.prototype.handleDownEvent = function (evt) {
    let map = evt.map;
    let feature = map.forEachFeatureAtPixel(evt.pixel, function (feature, layer) {
        if (feature.get('name') === 'start') {
            startFeature = feature;
            return feature;
        }
    });

    if (feature) {
        this.coordinate_ = evt.coordinate;
        this.feature_ = feature;
    }

    return !!feature;
};

// Function handling "drag" events. 
// This function is called on "move" events during a drag sequence.
app.Drag.prototype.handleDragEvent = function (evt) {
    let deltaX = evt.coordinate[0] - this.coordinate_[0];
    let deltaY = evt.coordinate[1] - this.coordinate_[1];

    let geometry = this.feature_.getGeometry();
    geometry.translate(deltaX, deltaY);
    this.coordinate_[0] = evt.coordinate[0];
    this.coordinate_[1] = evt.coordinate[1];
};

// Function handling "up" events.
// If the function returns false then the current drag sequence is stopped.
app.Drag.prototype.handleUpEvent = function (e) {
    const coord = this.feature_.getGeometry().getCoordinates();
    const featureType = this.feature_.get('name');
    let coord_ = [];
    switch (featureType) {
        case 'start':
            coord_ = ol.proj.transform(coord, 'EPSG:3857', 'EPSG:4326');
            startInputCoord = coord_.slice();
            updateInputValue();
            break;
    }
    performRouting();
    this.coordinate_ = null;
    this.feature_ = null;
    return false; // `false` to stop the drag sequence.

};


/*---------------------------------------------*/
// 7. Event Listeners
/*---------------------------------------------*/

// These event listeners tell the UI when it's time to execute all of the 
// code we've written.
document.addEventListener('DOMContentLoaded', function () {
    // Update the input box value by the default coordinates. 
    startInputEle = document.querySelector('#startPoint');
    updateInputValue();
    // When click on the map, hide the context menut.
    document.querySelector('#map').onclick = () => {
        hideOrShowContextMenu('hide');
    };

    // Hide the context menu of the browsers when right click on the map.
    document.querySelector('#map').oncontextmenu = () => {
        return false;
    };

    // Handle the click event when click the item in the customized context menu.
    document.querySelector('#ol-contextmenu').addEventListener('click', (e) => {
        e = window.event || e;
        const targetId = e.target.id;
        switch (targetId) {
            case 'add-start-point':
                hideOrShowContextMenu('hide');
                performRouting();
                updateInputValue();
                pointFeature.setGeometry(new ol.geom.Point(ol.proj.fromLonLat(startInputCoord)));
                break;
            case 'clear':
                clear();
                break;
        }
    });

    // Update the service area options in left sidebar control panel.
    document.querySelector('#serivce-area').addEventListener('click', function (e) {
        e = window.event || e;
        target = e.srcElement || e.target;
        if (target.nodeName === 'BUTTON') {
            document.querySelector('#serivce-area .active').classList.remove('active');
            target.classList.add('active');
            if (target.value === 'Time') {
                document.querySelector('#drive-time-limit').classList.remove('hide');
                document.querySelector('#drive-distance-limit').classList.add('hide');
            } else {
                document.querySelector('#drive-distance-limit').classList.remove('hide');
                document.querySelector('#drive-time-limit').classList.add('hide');
            }
        }
    });

    // Perform the routing service area request when click the "Refresh" button in the left sidebar panel.
    document.querySelector('#refresh').addEventListener('click', function () {
        const coord = startInputEle.value.replace(/\s/g, '');
        let coor_ = coord.split(',');
        let coord_ = [Number(coor_[1]), Number(coor_[0])];
        pointFeature.setGeometry(new ol.geom.Point(ol.proj.fromLonLat(coord_)));
        startInputCoord = coord_.slice();
        updateInputValue(true);
        performRouting();
    });

    // Hide or show the sidebar panel when the device is a mobile platform.
    document.querySelector('#toggle').addEventListener('click', function () {
        const classList = document.querySelector('.travelPanel').classList;
        classList.contains('collapsed') ? classList.remove('collapsed') : classList.add('collapsed');
    })

    // Update the Driving Minute Limit input min or max attribute value when changing the input value.
    let timeLimitInputs = document.querySelectorAll('#drive-time-limit input');
    for (let i = 0; i < timeLimitInputs.length; i++) {
        timeLimitInputs[i].addEventListener('input', function (e) {
            let value = Number(e.target.value);
            if (i === 0) {
                timeLimitInputs[i + 1].setAttribute('min', value + 1)
            } else if (i === 1 || i === 2) {
                timeLimitInputs[i + 1].setAttribute('min', value + 1)
                timeLimitInputs[i - 1].setAttribute('max', value - 1)
            } else if (i === 3) {
                timeLimitInputs[i - 1].setAttribute('max', value - 1)
            }
        });
    }

    // Update the Driving Distance Limit input min or max attribute value when changing the input value.
    let disLimitInputs = document.querySelectorAll('#drive-distance-limit input');
    for (let i = 0; i < disLimitInputs.length; i++) {
        disLimitInputs[i].addEventListener('input', function (e) {
            let value = Number(e.target.value);
            if (i === 0) {
                disLimitInputs[i + 1].setAttribute('min', value + 1)
            } else if (i === 1 || i === 2) {
                disLimitInputs[i + 1].setAttribute('min', value + 1)
                disLimitInputs[i - 1].setAttribute('max', value - 1)
            } else if (i === 3) {
                disLimitInputs[i - 1].setAttribute('max', value - 1)
            }
        });
    }
})
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.

Console