Home Reference Source

src/controls/StaticOverviewMap.js

import $ from 'jquery'

import { transformExtent } from 'ol/proj'
import Feature from 'ol/Feature'
import Map from 'ol/Map'
import View from 'ol/View'
import ImageLayer from 'ol/layer/Image'
import { ImageStatic } from 'ol/source'
import VectorSource from 'ol/source/Vector'
import Style from 'ol/style/Style'
import Stroke from 'ol/style/Stroke'
import RegularShape from 'ol/style/RegularShape'
import Fill from 'ol/style/Fill'
import Point from 'ol/geom/Point'

import { Control } from './Control'
import { VectorLayer } from '../layers/VectorLayer'

import '../../less/staticoverviewmap.less'
import Polygon from 'ol/geom/Polygon'

/**
 * @typedef {ControlOptions} StaticOverviewMapOptions
 * @property {String} url to the static picture
 * @property {Number[]} extent of the shown image
 * @property {ol.ProjectionLike} projection used
 */

export class StaticOverviewMap extends Control {
  /**
   * @param {StaticOverviewMapOptions} options
   */
  constructor (options = {}) {
    options.element = $('<div>')[0]
    options.className = options.className || 'g4u-static-overviewmap'

    super(options)

    this.doNotShowArrow_ = options.doNotShowArrow !== false

    this.$mapElement_ = $('<div>').addClass(this.getClassName() + '-map')

    this.get$Element().append(this.$mapElement_)

    this.collapsible_ = options.collapsible || true

    if (this.getCollapsible()) {
      this.get$Element()
        .append($('<button>')
          .on('click', () => this.setCollapsed(!this.getCollapsed()))
        )
    }

    this.setCollapsed(options.collapsed || false)

    this.projection = options.projection
    this.extent = options.extent
    this.url = options.url

    this.boundCalculateOrientationFeatures_ = () => this.calculateOrientationFeatures()
  }

  getCollapsible () {
    return this.collapsible_
  }

  setCollapsed (collapsed) {
    const oldValue = this.collapsed_
    if (oldValue !== collapsed) {
      this.collapsed_ = collapsed
      if (collapsed) {
        this.get$Element().addClass('ol-collapsed')
      } else {
        this.get$Element().removeClass('ol-collapsed')
        if (this.ovmap_) {
          this.ovmap_.updateSize()
          this.ovmap_.render()
        }
        this.calculateOrientationFeatures()
      }
      this.dispatchEvent({ type: 'change:collapsed', oldValue })
    }
  }

  getCollapsed () {
    return this.collapsed_
  }

  getOverviewMap () {
    return this.ovmap_
  }

  setMap (map) {
    // detach
    if (this.getMap()) {
      if (this.ovmap_) {
        this.ovmap_.un('postrender', this.boundCalculateOrientationFeatures_)
      }
      this.ovmap_ = null
      this.getMap().getView().un('change:center', this.boundCalculateOrientationFeatures_)
      this.getMap().getView().un('change:resolution', this.boundCalculateOrientationFeatures_)
    }

    super.setMap(map)

    // attach
    if (map) {
      map.asSoonAs('mobile', false, () => {
        const mapProjection = map.getView().getProjection()
        const transformedExtent = transformExtent(this.extent, this.projection, mapProjection)

        this.frame = new Feature()
        if (!this.doNotShowArrow_) {
          this.arrow = new Feature()
        }

        const fitViewToImage = () => {
          this.ovmap_.getView().fit(transformedExtent)
        }

        const setSize = (width, height) => {
          if (this.ovmap_) {
            this.$mapElement_.innerWidth(width)
            this.$mapElement_.innerHeight(height)
            this.ovmap_.updateSize()
            fitViewToImage()
          }
        }

        const features = [this.frame]
        if (!this.doNotShowArrow_) {
          features.push(this.arrow)
        }
        this.ovmap_ = new Map({
          target: this.$mapElement_[0],
          view: new View({
            projection: mapProjection,
            constrainResolution: false
          }),
          layers: [
            new ImageLayer({
              source: new ImageStatic({
                imageExtent: transformedExtent,
                url: this.url,
                imageLoadFunction: function (image, src) {
                  const $img = $(image.getImage())
                  $img.on('load', () => {
                    setSize($img.prop('width'), $img.prop('height'))
                  }).prop('src', src)
                }
              })
            }),
            new VectorLayer({
              source: new VectorSource({
                features: features
              }),
              style: new Style({
                stroke: new Stroke({
                  color: 'rgb(0,0,0)',
                  width: 2
                })
              })
            })
          ],
          interactions: [],
          controls: []
        })

        this.ovmap_.once('postrender', this.boundCalculateOrientationFeatures_)

        fitViewToImage()

        map.getView().on('change:center', this.boundCalculateOrientationFeatures_)
        map.getView().on('change:resolution', this.boundCalculateOrientationFeatures_)

        // interactivity

        let $overviewmap = this.get$Element().find('.' + this.getClassName() + '-map')

        $overviewmap = $overviewmap.add($overviewmap.find('.' + this.getClassName() + '-box'))

        let dontClick = false

        $overviewmap.on('click', e => {
          if (!dontClick) {
            map.getView().setCenter(this.getOverviewMap().getEventCoordinate(e))
          }
        })

        let mouseDown = false

        $overviewmap.on('mousedown', () => {
          dontClick = false
          mouseDown = true
        })

        $overviewmap.on('mouseup', () => {
          mouseDown = false
        })

        $overviewmap.on('mousemove', e => {
          if (mouseDown) {
            map.getView().setCenter(this.getOverviewMap().getEventCoordinate(e))
          }
        })

        this.getOverviewMap().getView().on('change:center', () => {
          mouseDown = false
          dontClick = true
        })
      })
    }
  }

  calculateOrientationFeatures () {
    if (this.getMap() && !this.getCollapsed()) {
      const extent = this.getMap().getView().calculateExtent(this.getMap().getSize())
      this.frame.setGeometry(new Polygon([[
        [extent[0], extent[1]],
        [extent[2], extent[1]],
        [extent[2], extent[3]],
        [extent[0], extent[3]],
        [extent[0], extent[1]]
      ]]))

      if (!this.doNotShowArrow_) {
        const mapCenter = this.getMap().getView().getCenter()

        const edgePoint = [mapCenter[0], extent[3]]
        const edgePointPixel = this.ovmap_.getPixelFromCoordinate(edgePoint)

        const arrowPointPixel = [edgePointPixel[0], edgePointPixel[1] - 7]

        const arrowPoint = this.ovmap_.getCoordinateFromPixel(arrowPointPixel)
        this.arrow.setGeometry(new Point(arrowPoint))

        this.arrow.setStyle(new Style({
          image: new RegularShape({
            fill: new Fill({
              color: 'rgb(0,0,0)'
            }),
            points: 3,
            radius: 5,
            stroke: new Stroke({
              width: 0,
              color: 'rgb(0,0,0)'
            }),
            rotation: 0
          })
        }))
      }
    }
  }
}