25 Mar 2019

Mini-map with Geolocation extension

This cool extension is available since version 6.4 (see this post) and brings a few functions to handle conversions between Viewer XYZ and LL84 Lat Long coordinate system. For this post, let's implement a mini-map with the current model position and bounding box. Let's call it MiniMapExtension and use the skeleton described here.

See it live here. Note you'll need a model with location.

For the code, first, we need a map with API, let's use Maps JavaScript. You'll need a developer key to include into your HTML file, like this:

<script async defer src="https://maps.googleapis.com/maps/api/js?key=YOU_KEY_HERE" type="text/javascript"></script>

The following code shows how to initialize a map. Note how the center is 0/0, we'll update it later.

// initialize the map
if (_this.map == null) {
    _this.map = new google.maps.Map(document.getElementById('googlemap'), {
        zoom: 16,
        center: { lat: 0, lng: 0 },
        mapTypeId: 'satellite',
        rotateControl: false,
        streetViewControl: false,
        tilt: 0
    });
    // draw model boundoung box & center
    var bb = _this.viewer.model.getBoundingBox();
    _this.drawBoundingBox(bb.min, bb.max);
    _this.cameraChanged(_this.viewer.autocam); // first run (center of the model)
}

Viewer gives us the bounding box (min/max) information, used above. The following code uses it to create a polygon and draw it using Maps Shapes API.

MiniMapExtension.prototype.drawBoundingBox = function (min, max) {
    // basic check...
    if (this.map == null) return;
    if (this.geoExtension == null) return;

    // prepare a polygon with the bounding box information
    var polygon = [];
    polygon.push({ x: min.x, y: min.y });
    polygon.push({ x: min.x, y: max.y });
    polygon.push({ x: max.x, y: max.y });
    polygon.push({ x: max.x, y: min.y });

    this.drawPolygon(polygon);
}

MiniMapExtension.prototype.drawPolygon = function (polygon) {
    // basic check...
    var _this = this;
    if (_this.map == null) return;
    if (_this.geoExtension == null) return;

    // prepare the polygon coordinate to draw it
    var coords = [];
    polygon.forEach(function (point) {
        var geoLoc = _this.geoExtension.lmvToLonLat(point);
        coords.push({ lat: geoLoc.y, lng: geoLoc.x });
    });
    var polyOptions = {
        path: coords,
        strokeColor: '#FF0000',
        strokeOpacity: 0.8,
        strokeWeight: 2,
        fillColor: '#FF0000',
        fillOpacity: 0.1,
    };
    var polygon = new google.maps.Polygon(polyOptions);
    polygon.setMap(_this.map);
}

The above code uses lmvToLonLat() function, which essentially converts from Viewer coordinates to LL84. The opposite can be done with lonLatToLmv(), but will leave that for a future experiment. A mini-map needs to sync with the model, right? So let's use the viewer.autocam.center point to position the map. To make it interactive, we need to addEventListener to CAMERA_CHANGED event.

MiniMapExtension.prototype.cameraChanged = function (camera) {
    // basic check...
    if (this.map == null) return;
    if (this.geoExtension == null) return;

    // adjust the center of the map
    var geoLoc = this.geoExtension.lmvToLonLat(camera.center);
    this.map.setCenter({ lat: geoLoc.y, lng: geoLoc.x });
}

Last, you need a CSS to define the button and maps and to make Viewer load it.

And get the entire extension code here.

Related Article