mirror of
https://github.com/peermaps/mixmap.git
synced 2025-02-23 07:24:00 +01:00
399 lines
10 KiB
JavaScript
399 lines
10 KiB
JavaScript
var html = require('bel')
|
|
var EventEmitter = require('events').EventEmitter
|
|
var onload = require('on-load')
|
|
var boxIntersect = require('box-intersect')
|
|
var defined = require('defined')
|
|
var bboxToZoom = require('./bbox-to-zoom.js')
|
|
var zoomToBbox = require('./zoom-to-bbox.js')
|
|
var Draw = require('./draw.js')
|
|
var assign = require('./assign.js')
|
|
|
|
var idlecb = window.requestIdleCallback
|
|
|| function (f) { setTimeout(f,0) }
|
|
|
|
var css = require('sheetify')
|
|
var style = css`
|
|
:host {
|
|
-webkit-touch-callout: none;
|
|
-webkit-user-select: none;
|
|
-khtml-user-select: none;
|
|
-moz-user-select: none;
|
|
-ms-user-select: none;
|
|
user-select: none;
|
|
}
|
|
`
|
|
|
|
module.exports = Map
|
|
|
|
function Map (rcom, opts) {
|
|
if (!(this instanceof Map)) return new Map(rcom, opts)
|
|
if (!opts) opts = {}
|
|
this._rcom = rcom
|
|
this.regl = rcom.regl
|
|
this._draw = []
|
|
this._layers = []
|
|
this._layerTiles = []
|
|
this._viewboxQueue = []
|
|
this._viewboxN = 0
|
|
this._pickInit = false
|
|
this._pickfbOptions = defined(opts.pickfb, {})
|
|
this.viewbox = opts.viewbox || [-180,-90,180,90]
|
|
this.backgroundColor = opts.backgroundColor || [1,1,1,1]
|
|
this._mouse = null
|
|
this._size = null
|
|
this.size = null
|
|
this.on('viewbox', this._onviewbox)
|
|
}
|
|
Map.prototype = Object.create(EventEmitter.prototype)
|
|
|
|
Map.prototype._viewboxQueueClear = function () {
|
|
this._viewboxN++
|
|
this._viewboxQueue = []
|
|
}
|
|
Map.prototype._viewboxEnqueue = function (fn) {
|
|
var self = this
|
|
self._viewboxQueue.push(fn)
|
|
var n = self._viewboxN
|
|
if (self._viewboxQueue.length === 1) next()
|
|
function next () {
|
|
if (n !== self._viewboxN) return
|
|
if (self._viewboxQueue.length === 0) return
|
|
self._viewboxQueue[0](function () {
|
|
if (n !== self._viewboxN) return
|
|
setTimeout(function () {
|
|
idlecb(function () {
|
|
if (n !== self._viewboxN) return
|
|
self._viewboxQueue.shift()
|
|
next()
|
|
})
|
|
}, 50)
|
|
})
|
|
}
|
|
}
|
|
|
|
Map.prototype._onviewbox = function () {
|
|
var self = this
|
|
if (self._idle) return
|
|
self._idle = setTimeout(function () {
|
|
idlecb(ready)
|
|
}, 100)
|
|
function ready () {
|
|
self._idle = null
|
|
self._viewboxQueueClear()
|
|
var boxes = []
|
|
var x0 = Math.floor((self.viewbox[0]+180)/360)*360
|
|
var x1 = Math.floor((self.viewbox[2]+180)/360)*360
|
|
for (var x = x0; x <= x1; x += 360) {
|
|
boxes.push([
|
|
self.viewbox[0]-x, self.viewbox[1],
|
|
self.viewbox[2]-x, self.viewbox[3] ])
|
|
}
|
|
var zoom = self.getZoom()
|
|
var pending = self._layers.length
|
|
function done () {
|
|
if (self._viewboxpending > 1) {
|
|
setTimeout(function () {
|
|
idlecb(function () {
|
|
self._viewboxpending = 0
|
|
ready()
|
|
})
|
|
}, 50)
|
|
} else {
|
|
self._viewboxpending = 0
|
|
}
|
|
}
|
|
for (var i = 0; i < self._layers.length; i++) (function (layer,tiles) {
|
|
layer.viewbox(self.viewbox, zoom, function (err, lboxes) {
|
|
if (err) {
|
|
if (--pending === 0) done()
|
|
return self.emit('error', err)
|
|
}
|
|
if (!lboxes) lboxes = []
|
|
var changed = false
|
|
var lkeys = Object.keys(lboxes)
|
|
var step = 10
|
|
var active = {}
|
|
for (var i = 0; i < lkeys.length; i+=step) (function (keys) {
|
|
self._viewboxEnqueue(function (cb) {
|
|
var values = keys.map(function (key) { return lboxes[key] })
|
|
boxIntersect(boxes, values, function (j, k) {
|
|
var key = keys[k], bbox = values[k]
|
|
active[key] = true
|
|
if (tiles[key]) return
|
|
tiles[key] = bbox
|
|
layer.add(key, bbox)
|
|
changed = true
|
|
})
|
|
cb()
|
|
})
|
|
})(lkeys.slice(i,i+step))
|
|
self._viewboxEnqueue(function (cb) {
|
|
Object.keys(tiles).forEach(function (key) {
|
|
if (tiles[key] && !active[key]) {
|
|
layer.remove(key, tiles[key])
|
|
changed = true
|
|
tiles[key] = null
|
|
}
|
|
})
|
|
if (changed) self.draw()
|
|
if (--pending === 0) done()
|
|
cb()
|
|
})
|
|
})
|
|
})(self._layers[i],self._layerTiles[i])
|
|
}
|
|
}
|
|
|
|
Map.prototype.prop = function (name) {
|
|
return this.regl.prop(name)
|
|
}
|
|
|
|
Map.prototype.createDraw = function (opts) {
|
|
var self = this
|
|
if (!opts) throw new Error('must provide layer information to add()')
|
|
var drawOpts = assign({
|
|
frag: `
|
|
precision highp float;
|
|
void main () {
|
|
gl_FragColor = vec4(1,0,0,1);
|
|
}
|
|
`,
|
|
vert: `
|
|
precision highp float;
|
|
attribute vec2 position;
|
|
varying vec2 vpos;
|
|
uniform vec4 viewbox;
|
|
uniform vec2 offset;
|
|
uniform float zindex, aspect;
|
|
void main () {
|
|
vec2 p = position + offset;
|
|
vpos = position;
|
|
gl_Position = vec4(
|
|
(p.x - viewbox.x) / (viewbox.z - viewbox.x) * 2.0 - 1.0,
|
|
((p.y - viewbox.y) / (viewbox.w - viewbox.y) * 2.0 - 1.0) * aspect,
|
|
1.0/(2.0+zindex), 1);
|
|
}
|
|
`,
|
|
}, opts, {
|
|
uniforms: assign({
|
|
viewbox: self.prop('viewbox'),
|
|
zoom: self.prop('zoom'),
|
|
aspect: function (context) {
|
|
return context.viewportWidth / context.viewportHeight
|
|
},
|
|
zindex: 0,
|
|
offset: self.prop('offset')
|
|
}, opts.uniforms)
|
|
})
|
|
var draw = new Draw(drawOpts, {
|
|
regl: self.regl,
|
|
onremove: function () {
|
|
var ix = self._draw.indexOf(draw)
|
|
if (ix >= 0) self._draw.splice(ix,1)
|
|
}
|
|
})
|
|
self._draw.push(draw)
|
|
self.draw()
|
|
return draw
|
|
}
|
|
|
|
Map.prototype.addLayer = function (opts) {
|
|
var self = this
|
|
self._layers.push(opts)
|
|
self._layerTiles.push({})
|
|
if (!self._layerIdle) {
|
|
self._layerIdle = idlecb(function () {
|
|
self._layerIdle = null
|
|
self._onviewbox()
|
|
})
|
|
}
|
|
}
|
|
|
|
Map.prototype._props = function () {
|
|
var x0 = Math.floor((this.viewbox[0]+180)/360)*360
|
|
var x1 = Math.floor((this.viewbox[2]+180)/360)*360
|
|
var props = []
|
|
var zoom = this.getZoom()
|
|
for (var x = x0; x <= x1; x+= 360) {
|
|
props.push({ viewbox: this.viewbox, zoom: zoom, offset: [x,0] })
|
|
}
|
|
return props
|
|
}
|
|
|
|
Map.prototype.draw = function () {
|
|
this.regl.poll()
|
|
this.regl.clear({ color: this.backgroundColor, depth: true })
|
|
var props = this._props()
|
|
for (var i = 0; i < this._draw.length; i++) {
|
|
this._draw[i].draw(props)
|
|
}
|
|
}
|
|
|
|
Map.prototype.pick = function (opts, cb) {
|
|
if (!opts) opts = {}
|
|
if (!cb) cb = function () {}
|
|
var self = this
|
|
var ex = defined(opts.offsetX, opts.x, 0)
|
|
var ey = defined(opts.offsetY, opts.y, 0)
|
|
var x = Math.max(0, Math.min(self._size[0]-1, ex))
|
|
var y = Math.max(0, Math.min(self._size[1]-1, self._size[1] - ey))
|
|
var first = false
|
|
if (!self._fb) {
|
|
self._fb = self.regl.framebuffer(self._pickfbOptions)
|
|
for (var i = 0; i < self._draw.length; i++) {
|
|
self._draw[i]._setfb(self._fb)
|
|
}
|
|
}
|
|
if (self._pickInit === false) {
|
|
self._pickInit = true
|
|
self._fb.resize(self._size[0], self._size[1])
|
|
self._fb.use(function () {
|
|
var props = self._props()
|
|
for (var i = 0; i < self._draw.length; i++) {
|
|
self._draw[i].pick(props)
|
|
}
|
|
idlecb(function () {
|
|
self.pick(opts, cb)
|
|
})
|
|
})
|
|
return
|
|
}
|
|
idlecb(function () {
|
|
self.regl.poll()
|
|
self._fb.resize(self._size[0], self._size[1])
|
|
self.regl.clear({
|
|
color: [0,0,0,0],
|
|
depth: true,
|
|
framebuffer: self._fb
|
|
})
|
|
self._fb.use(function () {
|
|
var props = self._props()
|
|
for (var i = 0; i < self._draw.length; i++) {
|
|
self._draw[i].pick(props)
|
|
}
|
|
var data = self.regl.read({
|
|
framebuffer: self._fb,
|
|
x: x,
|
|
y: y,
|
|
width: defined(opts.width, 1),
|
|
height: defined(opts.height, 1)
|
|
})
|
|
cb(null, data)
|
|
})
|
|
})
|
|
}
|
|
|
|
Map.prototype._setMouse = function (ev) {
|
|
var self = this
|
|
var x = ev.offsetX
|
|
var y = ev.offsetY
|
|
var b = ev.buttons & 1
|
|
if (!self._mouse) {
|
|
self._mouse = [0,0]
|
|
} else if (b && self._size) {
|
|
var aspect = self._size[0] / self._size[1]
|
|
self.move(
|
|
(self._mouse[0]-x)/self._size[0],
|
|
(self._mouse[1]-y)/self._size[1] / aspect
|
|
)
|
|
}
|
|
self._mouse[0] = x
|
|
self._mouse[1] = y
|
|
}
|
|
|
|
Map.prototype.move = function (dx,dy) {
|
|
var self = this
|
|
var w = self.viewbox[2] - self.viewbox[0]
|
|
var h = self.viewbox[3] - self.viewbox[1]
|
|
self.viewbox[0] += dx*w
|
|
self.viewbox[1] -= dy*h
|
|
self.viewbox[2] += dx*w
|
|
self.viewbox[3] -= dy*h
|
|
self.draw()
|
|
self.emit('viewbox', self.viewbox)
|
|
}
|
|
|
|
Map.prototype.setViewbox = function (viewbox) {
|
|
var self = this
|
|
self.viewbox = viewbox
|
|
self.emit('viewbox', self.viewbox)
|
|
}
|
|
|
|
Map.prototype._fixbbox = function () {
|
|
if (this.viewbox[1] < -90) {
|
|
this.viewbox[3] += -90 - this.viewbox[1]
|
|
this.viewbox[1] = -90
|
|
}
|
|
if (this.viewbox[3] > 90) {
|
|
this.viewbox[1] += 90 - this.viewbox[3]
|
|
this.viewbox[3] = 90
|
|
}
|
|
}
|
|
|
|
Map.prototype.render = function (props) {
|
|
if (!props) props = {}
|
|
var self = this
|
|
var cstyle = `
|
|
width: ${props.width}px;
|
|
height: ${props.height}px;
|
|
`
|
|
if (!this._size) {
|
|
this._size = [0,0]
|
|
this.size = this._size
|
|
}
|
|
this._size[0] = props.width
|
|
this._size[1] = props.height
|
|
this.draw()
|
|
this._relement = this._rcom.render(props)
|
|
if (props.mouse === false) {
|
|
this.element = html`<div class=${style} style=${cstyle}>
|
|
<div>${this._relement}</div>
|
|
</div>`
|
|
} else {
|
|
this.element = html`<div class=${style} style=${cstyle}>
|
|
<div onmouseover=${move} onmouseout=${move}
|
|
onmousemove=${move} onmousedown=${move} onmouseup=${move}>
|
|
${this._relement}
|
|
</div>
|
|
</div>`
|
|
}
|
|
onload(this.element,
|
|
function () { self._load() },
|
|
function () { self._unload() })
|
|
return this.element
|
|
function move (ev) { self._setMouse(ev) }
|
|
}
|
|
|
|
Map.prototype.resize = function (width, height) {
|
|
this._size[0] = width
|
|
this._size[1] = height
|
|
this.element.style.width = width + 'px'
|
|
this.element.style.height = height + 'px'
|
|
var r = this._rcom.render({ width: width, height: height })
|
|
this._relement = r
|
|
this.emit('resize', width, height)
|
|
}
|
|
|
|
Map.prototype._load = function () {
|
|
if (this._unloaded) {
|
|
this._unloaded = false
|
|
for (var i = 0; i < this._draw.length; i++) {
|
|
this._draw[i].reload()
|
|
}
|
|
}
|
|
this.draw()
|
|
}
|
|
|
|
Map.prototype._unload = function () {
|
|
this._unloaded = true
|
|
}
|
|
|
|
Map.prototype.getZoom = function () {
|
|
return bboxToZoom(this.viewbox)
|
|
}
|
|
|
|
Map.prototype.setZoom = function (n) {
|
|
zoomToBbox(this.viewbox, Math.max(Math.min(n,21),1))
|
|
this.draw()
|
|
this.emit('viewbox', this.viewbox)
|
|
}
|