tmcgee/cmv-widgets

View on GitHub
widgets/Mapillary.js

Summary

Maintainability
A
0 mins
Test Coverage
define([
    'dojo/_base/declare',
    'dijit/_WidgetBase',
    'dijit/_TemplatedMixin',
    'dijit/_WidgetsInTemplateMixin',

    'dojo/_base/lang',
    'dojo/topic',
    'dojo/on',
    'dojo/aspect',
    'dojo/dom-style',
    'dojo/dom-geometry',

    'esri/geometry/Point',
    'esri/SpatialReference',

    'esri/graphic',

    'esri/layers/GraphicsLayer',
    'esri/layers/VectorTileLayer',
    'esri/symbols/TextSymbol',
    'esri/renderers/SimpleRenderer',

    'dijit/MenuItem',

    'proj4js/proj4',

    'dojo/text!./Mapillary/templates/Mapillary.html',
    'dojo/i18n!./Mapillary/nls/Mapillary',

    'https://unpkg.com/mapillary-js@2.5.2/dist/mapillary.min.js',
    'xstyle/css!https://unpkg.com/mapillary-js@2.5.2/dist/mapillary.min.css',

    'xstyle/css!./Mapillary/css/Mapillary.css',

    'dijit/form/ToggleButton',
    'dijit/form/CheckBox'

], function (
    declare,
    _WidgetBase,
    _TemplatedMixin,
    _WidgetsInTemplateMixin,

    lang,
    topic,
    on,
    aspect,
    domStyle,
    domGeom,

    Point,
    SpatialReference,

    Graphic,

    GraphicsLayer,
    VectorTileLayer,
    TextSymbol,
    SimpleRenderer,

    MenuItem,
    proj4,

    template,
    i18n,

    Mapillary

) {
    return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
        widgetsInTemplate: true,
        templateString: template,
        i18n: i18n,
        baseClass: 'cmvMapillaryWidget',

        mapClickMode: null,

        // in case this changes some day
        proj4BaseURL: 'https://epsg.io/',

        //  options are ESRI, EPSG and SR-ORG
        // See http://sepsg.io/ for more information
        proj4Catalog: 'EPSG',

        // if desired, you can load a projection file from your server
        // instead of using one from epsg.io
        // i.e., http://server/projections/102642.js
        proj4CustomURL: null,

        domID: 'mapillary-node',

        mapillaryOptions: {},
        defaultMapillaryOptions: {
            clientID: null,
            photoID: null,
            options: {
                baseImageSize: Mapillary.ImageSize.Size320,
                basePanoramaSize: Mapillary.ImageSize.Size1024,
                component: {
                    cache: false,
                    cover: false,
                    direction: true
                },
                maxImageSize: Mapillary.ImageSize.Size1024,
                renderMode: Mapillary.RenderMode.Letterbox
            }
        },

        textSymbolOptions: {
            angle: 322.5,
            color: [0, 128, 200],
            text: '\uf124',
            font: {
                size: 16,
                family: 'Font Awesome 5 Free'
            }
        },

        layerOptions: {},
        defaultLayerOptions: {
            url: require.toUrl('./widgets/Mapillary/mapillary-style.json'),
            id: 'mapillary',
            opacity: 0.6,
            visible: false
        },

        visibleOnFirstOpen: true,

        startup: function () {
            this.inherited(arguments);

            this.mapillaryOptions = this.mixinDeep(this.defaultMapillaryOptions, this.mapillaryOptions);
            this.layerOptions = this.mixinDeep(this.defaultLayerOptions, this.layerOptions);

            this.createMapillary();

            this.createGraphicsLayer();
            this.createVectorLayer();

            this.own(topic.subscribe('mapClickMode/currentSet', lang.hitch(this, 'setMapClickMode')));

            this.map.on('click', lang.hitch(this, 'getMapillary'));

            if (this.parentWidget) {
                if (this.parentWidget.toggleable) {
                    if (this.parentWidget.get('open') && this.visibleOnFirstOpen) {
                        this.layer.setVisibility(true);
                    }
                    this.own(aspect.after(this.parentWidget, 'toggle', lang.hitch(this, function () {
                        this.onLayoutChange(this.parentWidget.open);
                    })));
                }
                this.own(aspect.after(this.parentWidget, 'resize', lang.hitch(this, 'resize')));
                this.own(topic.subscribe(this.parentWidget.id + '/resize/resize', lang.hitch(this, 'resize')));
            }

            if (!window.proj4) {
                window.proj4 = proj4;
            }

            if (this.mapRightClickMenu) {
                this.addRightClickMenu();
            }

        },

        createMapillary: function () {
            this.mapillary = new Mapillary.Viewer(
                this.domID,
                this.mapillaryOptions.clientID,
                this.mapillaryOptions.photoID,
                this.mapillaryOptions.options
            );
            this.mapillary.on(Mapillary.Viewer.nodechanged, lang.hitch(this, 'onNodeChanged'));
            this.mapillary.on(Mapillary.Viewer.bearingchanged, lang.hitch(this, 'onBearingChanged'));
        },

        createVectorLayer: function () {
            this.layer = new VectorTileLayer(this.layerOptions.url, this.layerOptions);
            this.map.addLayer(this.layer);

            this.layer.on('visibility-change', lang.hitch(this, function () {
                this.checkLayerVisible.set('checked', this.layer.visible);
                this.visibleOnFirstOpen = false;
            }));

            this.checkLayerVisible.set('checked', this.layer.visible);
            on(this.checkLayerVisible, 'change', lang.hitch(this, function () {
                this.layer.setVisibility(this.checkLayerVisible.get('checked'));
            }));
        },

        createGraphicsLayer: function () {
            this.pointGraphics = new GraphicsLayer({
                id: 'mapillary_graphics',
                title: 'Mapillary'
            });

            this.pointSymbol = new TextSymbol(this.textSymbolOptions);
            this.originalAngle = this.pointSymbol.angle;
            this.pointRenderer = new SimpleRenderer(this.pointSymbol);
            this.pointRenderer.label = 'Mapillary';
            this.pointRenderer.description = 'Mapillary';
            this.pointGraphics.setRenderer(this.pointRenderer);
            this.map.addLayer(this.pointGraphics);
        },

        addRightClickMenu: function () {
            this.map.on('MouseDown', lang.hitch(this, function (evt) {
                this.mapRightClickPoint = evt.mapPoint;
            }));
            this.mapRightClickMenu.addChild(new MenuItem({
                label: this.i18n.rightClickMenuItem.label,
                onClick: lang.hitch(this, 'mapillaryFromMapRightClick')
            }));
        },

        resize: function (options) {
            if (options && options.h) {
                domGeom.setContentSize(this.containerNode, {
                    h: (options.h - 2)
                });
            }
            this.mapillary.resize();
        },

        getMapillary: function (evt, overRide) {
            if (this.mapClickMode === 'mapillary' || overRide) {
                var mapPoint = evt.mapPoint;
                if (!mapPoint) {
                    return;
                }

                if (this.parentWidget && !this.parentWidget.open) {
                    this.parentWidget.toggle();
                }

                this.disableMapClick();

                // convert the map point's coordinate system into lat/long
                var geometry = null,
                    wkid = mapPoint.spatialReference.wkid;
                if (wkid === 102100) {
                    wkid = 3857;
                }
                var key = this.proj4Catalog + ':' + String(wkid);
                if (!proj4.defs[key]) {
                    var url = this.proj4CustomURL || this.proj4BaseURL + String(wkid) + '.js';
                    require([url], lang.hitch(this, 'getMapillary', evt, true));
                    return;
                }
                // only need one projection as we are
                // converting to WGS84 lat/long
                var projPoint = proj4(proj4.defs[key]).inverse([mapPoint.x, mapPoint.y]);
                if (projPoint) {
                    geometry = {
                        x: projPoint[0],
                        y: projPoint[1]
                    };
                }

                domStyle.set(this.mapillaryInstructions, 'display', 'none');
                if (geometry) {
                    domStyle.set(this.noMapillaryResults, 'display', 'none');
                    domStyle.set(this.mapillaryNode, 'display', 'block');
                    this.getMapillaryLocation(geometry);
                } else {
                    this.clearGraphics();
                    domStyle.set(this.noMapillaryResults, 'display', 'block');
                    domStyle.set(this.mapillaryNode, 'display', 'none');
                }
            }
        },

        getMapillaryLocation: function (geometry) {
            var promise = this.mapillary.moveCloseTo(geometry.y, geometry.x);
            promise.catch(lang.hitch(this, function () {
                this.clearGraphics();
                domStyle.set(this.noMapillaryResults, 'display', 'block');
                domStyle.set(this.mapillaryNode, 'display', 'none');
            }));
        },

        mapillaryFromMapRightClick: function () {
            var evt = {
                mapPoint: this.mapRightClickPoint
            };
            this.getMapillary(evt, true);
        },

        onNodeChanged: function (node) {
            var lng = node.latLon.lon;
            var lat = node.latLon.lat;

            this.setPlaceMarkerPosition(lat, lng);
            this.mapillary.resize();
        },

        onBearingChanged: function (bearing) {
            if (this.placeMarker) {
                var newBearing = bearing - this.originalAngle;
                if (newBearing < 0) {
                    newBearing = 360 + newBearing;
                }
                this.pointSymbol.setAngle(newBearing);
                this.pointGraphics.refresh();
            }
        },

        onOpen: function () {
            this.pointGraphics.show();
            if (this.visibleOnFirstOpen) {
                this.layer.setVisibility(true);
            }
        },

        onClose: function () {
            // end mapillary on close of title pane
            this.pointGraphics.hide();
            this.layer.setVisibility(false);
            if (this.mapClickMode === 'mapillary') {
                this.connectMapClick();
            }
        },
        onLayoutChange: function (open) {
            if (open) {
                this.onOpen();
            } else {
                this.onClose();
            }
        },

        placePoint: function () {
            if (this.mapillaryButtonDijit.get('checked')) {
                this.enableMapClick();
            } else {
                this.disableMapClick();
            }
        },

        enableMapClick: function () {
            this.mapillaryButtonDijit.set('checked', true);
            this.map.setMapCursor('crosshair');
            this.layer.setVisibility(true);
            topic.publish('mapClickMode/setCurrent', 'mapillary');
        },

        disableMapClick: function () {
            this.mapillaryButtonDijit.set('checked', false);
            this.map.setMapCursor('auto');
            topic.publish('mapClickMode/setDefault');
        },

        clearGraphics: function () {
            this.pointGraphics.clear();
            domStyle.set(this.noMapillaryResults, 'display', 'block');
        },

        setPlaceMarkerPosition: function (lat, lng) {
            if (!this.placeMarker || this.pointGraphics.graphics.length === 0) {
                this.placeMarker = new Graphic();
                // Add graphic to the map
                this.pointGraphics.add(this.placeMarker);
            }
            // get the new lat/long from streetview
            // Make sure they are numbers
            if (!isNaN(lat) && !isNaN(lng)) {
                // convert the resulting lat/long to the map's spatial reference
                var xy = null,
                    wkid = this.map.spatialReference.wkid;
                if (wkid === 102100) {
                    wkid = 3857;
                }
                var key = this.proj4Catalog + ':' + String(wkid);
                if (!proj4.defs[key]) {
                    var url = this.proj4CustomURL || this.proj4BaseURL + String(wkid) + '.js';
                    require([url], lang.hitch(this, 'setPlaceMarkerPosition'));
                    return;
                }
                // only need the one projection as we are
                // converting from WGS84 lat/long
                xy = proj4(proj4.defs[key]).forward([lng, lat]);
                if (xy) {
                    var point = new Point(xy, new SpatialReference({
                        wkid: wkid
                    }));

                    // change point position on the map
                    this.placeMarker.setGeometry(point);
                }
            }
        },

        setMapClickMode: function (mode) {
            this.mapClickMode = mode;
        },

        mixinDeep: function (dest, source) {
            //Recursively mix the properties of two objects
            var empty = {};
            for (var name in source) {
                if (!(name in dest) || (dest[name] !== source[name] && (!(name in empty) || empty[name] !== source[name]))) {
                    try {
                        if (source[name].constructor === Object) {
                            dest[name] = this.mixinDeep(dest[name], source[name]);
                        } else {
                            dest[name] = source[name];
                        }
                    } catch (e) {
                        // Property in destination object not set. Create it and set its value.
                        dest[name] = source[name];
                    }
                }
            }
            return dest;
        }

    });
});