Home Reference Source Repository

js/dashboard/map.js

import $ from 'jquery';
import API from 'api.light';
import log from 'logger';
import popupTpl from 'templates/map/coverage-popup.hbs';
import L from 'leaflet';
import Spinner from 'spin.js';
import 'vendor/leaflet.spin';

const DEFAULTS = {
        style: {
            clickable: true,
            weight: 0.5,
            opacity: 0.5,
            fillOpacity: 0.3
        },
        hover_style: {
            fillOpacity: 0.8
        }
    },
    LEVELS_URL = '/spatial/levels',
    COVERAGE_URL = '/spatial/coverage/{level}',
    ATTRIBUTIONS = [
        '©',
        '<a href="http://openstreetmap.org">OpenStreetMap</a>',
        '/',
        '<a href="http://open.mapquest.com/">MapQuest</a>'
    ].join(' '),
    TILES_PREFIX = location.protocol === 'https:' ? '//otile{s}-s' : '//otile{s}',
    TILES_URL = TILES_PREFIX + '.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
    TILES_CONFIG = {subdomains: '1234', attribution: ATTRIBUTIONS};


if (!window.Spinner) { // Fix for leaflet.spin
    window.Spinner = Spinner;
}

function sort_levels(a, b) {
    return a.position - b.position;
}

function random_color() {
    let letters = '0123456789ABCDEF'.split(''),
        color = '#';

    for (let i = 0; i < 6; i++) {
        color += letters[Math.round(Math.random() * 15)];
    }
    return color;
}

export default class CoverageMap {
    constructor(el, opts) {
        this.$el = $(el);
        this.opts = $.extend(true, {}, DEFAULTS, opts);
    }

    load() {
        log.debug('Loading coverage map');
        this.map = L.map(this.$el[0]);
        this.map.spin(true);
        API.get(LEVELS_URL, this.on_levels_loaded.bind(this));
    }

    on_levels_loaded(data) {
        let layers = {};

        this.levels = data.sort(sort_levels).map((level, idx) => {
            let layer = L.geoJson(null, {
                style: this.opts.style,
                onEachFeature: this.on_each_feature.bind(this)
            });
            level.data_url = this.layer_url(level);
            level.layer = layer;

            layer.level = level;
            layers[level.name] = layer;
            return level;
        });

        L.tileLayer(TILES_URL, TILES_CONFIG).addTo(this.map);

        L.control.layers(layers, null, {collapsed: false}).addTo(this.map);
        this.map.on('baselayerchange', (ev) => {
            this.switch_level(ev.layer.level);
        });

        this.switch_level(this.levels[0]);
    }

    /**
     * Display a given level
     */
    switch_level(level) {
        if (typeof level == 'string' || level instanceof String) {
            level = $.grep(this.levels, function(current_level) {
                return current_level.id == level;
            })[0];
        }

        if (level.layer == this.layer) {
            // already set
            return;
        }

        this.map.spin(true);

        this.children_level = $.grep(this.levels, function(l) {
            return l.parent == level.id;
        });

        if (this.layer && this.map.hasLayer(this.layer)) {
            this.map.removeLayer(this.layer);
        }
        this.layer = level.layer;
        if (!this.map.hasLayer(level.layer)) {
            level.layer.addTo(this.map);
        }

        if (!level.layer.loaded) {
            API.get(level.data_url, $.proxy(function(data) {
                level.layer.loaded = true;
                this.layer.addData(data);
                this.data_loaded();
            }, this));
        } else {
            this.data_loaded();
        }
    }

    layer_url(level) {
        return COVERAGE_URL.replace('{level}', level.id);
    }

    /**
     * End the loading state and center the map on the chosen bounds
     */
    data_loaded() {
        this.map.fitBounds(this.bounds || this.layer.getBounds());
        while(this.map._spinner) {
            this.map.spin(false);
        }
        this.bounds = null;
    }

    /**
     * Apply style to each feature:
     */
    on_each_feature(feature, layer) {
        let cls = this,
            base_color = random_color(),
            $popup = $(popupTpl({feature:feature, children: this.children_level}));

        layer.setStyle($.extend({}, cls.opts.style, {
            color: base_color,
            fillColor: base_color
        }));

        $popup.find('.drill-down').click(layer, this.on_feature_popup_click.bind(this));

        layer.bindPopup($popup[0]);
        layer.on('mouseover', function () {
            layer.setStyle(cls.opts.hover_style);
        });
        layer.on('mouseout', function () {
            layer.setStyle(cls.opts.style);
        });
    }

    on_feature_popup_click(ev, feature) {
        this.bounds = ev.data.getBounds();
        this.switch_level($(ev.target).data('level'));
        return false;
    }
};