mirror of
https://bitbucket.org/s_l_teichmann/mtsatellite
synced 2024-11-08 11:10:27 +01:00
Merged 'websocket' branch back into 'default'.
This commit is contained in:
commit
d0b0ba83ba
18
SETUP.md
18
SETUP.md
|
@ -104,7 +104,8 @@ in the background. To start `mtwebmapper` use:
|
||||||
-map=/path/to/your/map \
|
-map=/path/to/your/map \
|
||||||
-web=/path/to/your/static/web \
|
-web=/path/to/your/static/web \
|
||||||
-redis-host=localhost \
|
-redis-host=localhost \
|
||||||
-workers=2
|
-workers=2 \
|
||||||
|
-websockets=false
|
||||||
|
|
||||||
For the `colors=` options applys the same as said above. You can also add
|
For the `colors=` options applys the same as said above. You can also add
|
||||||
`-transparent=true` for transparency as mentioned above. The `web-host=` is the interface the
|
`-transparent=true` for transparency as mentioned above. The `web-host=` is the interface the
|
||||||
|
@ -125,6 +126,20 @@ give to much ressources to this if you planning to run the mapping webserver on
|
||||||
same machine as the Minetest server. On the other hand assigning more cores to it definitely
|
same machine as the Minetest server. On the other hand assigning more cores to it definitely
|
||||||
helps to boost up the performance.
|
helps to boost up the performance.
|
||||||
|
|
||||||
|
Setting the `-websockets=true` flag enables websocket support for the server. With this
|
||||||
|
feature turned on and changing the line (in `web/index.html`) from
|
||||||
|
|
||||||
|
var useWebsocket = false; // Set to true if you want websocket support
|
||||||
|
|
||||||
|
to
|
||||||
|
|
||||||
|
var useWebsocket = true; // Set to true if you want websocket support
|
||||||
|
|
||||||
|
the web client gets an extra 'auto update' button. When switched on the server
|
||||||
|
informs the client if something in the maps has changed. The displayed map will
|
||||||
|
then update automatically without the need of manual pressing the 'update view'
|
||||||
|
button. Of cause your browser needs Websocket support, too.
|
||||||
|
|
||||||
## Configure and restart the Minetest server
|
## Configure and restart the Minetest server
|
||||||
|
|
||||||
Now everything is in place and the only thing left ist to re-configure the Minetest server
|
Now everything is in place and the only thing left ist to re-configure the Minetest server
|
||||||
|
@ -138,4 +153,3 @@ backend with a Redis configuration:
|
||||||
You may have to set `redis_port` too if you run `mtredisalize` not on port 6379.
|
You may have to set `redis_port` too if you run `mtredisalize` not on port 6379.
|
||||||
|
|
||||||
Now we are all done and you can fire your Minetest server up again. :-)
|
Now we are all done and you can fire your Minetest server up again. :-)
|
||||||
|
|
||||||
|
|
112
cmd/mtwebmapper/forwardupdates.go
Normal file
112
cmd/mtwebmapper/forwardupdates.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// Copyright 2014 by Sascha L. Teichmann
|
||||||
|
// Use of this source code is governed by the MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type websocketForwarder struct {
|
||||||
|
upgrader *websocket.Upgrader
|
||||||
|
register chan *connection
|
||||||
|
unregister chan *connection
|
||||||
|
broadcast chan map[xz]bool
|
||||||
|
connections map[*connection]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type connection struct {
|
||||||
|
ws *websocket.Conn
|
||||||
|
send chan []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWebsocketForwarder() *websocketForwarder {
|
||||||
|
upgrader := &websocket.Upgrader{ReadBufferSize: 512, WriteBufferSize: 2048}
|
||||||
|
return &websocketForwarder{
|
||||||
|
upgrader: upgrader,
|
||||||
|
register: make(chan *connection),
|
||||||
|
unregister: make(chan *connection),
|
||||||
|
broadcast: make(chan map[xz]bool),
|
||||||
|
connections: make(map[*connection]bool)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsf *websocketForwarder) run() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case c := <-wsf.register:
|
||||||
|
wsf.connections[c] = true
|
||||||
|
case c := <-wsf.unregister:
|
||||||
|
if _, ok := wsf.connections[c]; ok {
|
||||||
|
delete(wsf.connections, c)
|
||||||
|
close(c.send)
|
||||||
|
}
|
||||||
|
case changes := <-wsf.broadcast:
|
||||||
|
if len(wsf.connections) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Do the JSON encoding this late to save
|
||||||
|
// some CPU cyles if no client is connected.
|
||||||
|
xzs := make([]xz, 0, len(changes))
|
||||||
|
for xz := range changes {
|
||||||
|
xzs = append(xzs, xz)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
encoder := json.NewEncoder(&buf)
|
||||||
|
if err := encoder.Encode(xzs); err != nil {
|
||||||
|
log.Printf("encoding changes failed: %s\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
msg := buf.Bytes()
|
||||||
|
for c := range wsf.connections {
|
||||||
|
select {
|
||||||
|
case c.send <- msg:
|
||||||
|
default:
|
||||||
|
delete(wsf.connections, c)
|
||||||
|
close(c.send)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsf *websocketForwarder) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := wsf.upgrader.Upgrade(rw, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Cannot upgrade to websocket: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c := &connection{ws: ws, send: make(chan []byte, 8)}
|
||||||
|
wsf.register <- c
|
||||||
|
defer func() { wsf.unregister <- c }()
|
||||||
|
go c.writer()
|
||||||
|
c.reader()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wsf *websocketForwarder) BaseTilesUpdated(changes map[xz]bool) {
|
||||||
|
wsf.broadcast <- changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connection) writer() {
|
||||||
|
defer c.ws.Close()
|
||||||
|
for msg := range c.send {
|
||||||
|
if c.ws.WriteMessage(websocket.TextMessage, msg) != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *connection) reader() {
|
||||||
|
defer c.ws.Close()
|
||||||
|
for {
|
||||||
|
// Just read the message and ignore it.
|
||||||
|
if _, _, err := c.ws.NextReader(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,32 +10,12 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ipsFromHosts(hosts string) ([]net.IP, error) {
|
|
||||||
|
|
||||||
ips := []net.IP{}
|
|
||||||
|
|
||||||
if len(hosts) == 0 { // Empty list: allow all hosts.
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, host := range strings.Split(hosts, ";") {
|
|
||||||
if hips, err := net.LookupIP(host); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
ips = append(ips, hips...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var (
|
var (
|
||||||
webPort int
|
webPort int
|
||||||
|
@ -48,6 +28,7 @@ func main() {
|
||||||
workers int
|
workers int
|
||||||
transparent bool
|
transparent bool
|
||||||
updateHosts string
|
updateHosts string
|
||||||
|
websockets bool
|
||||||
)
|
)
|
||||||
flag.IntVar(&webPort, "web-port", 8808, "port of the web server")
|
flag.IntVar(&webPort, "web-port", 8808, "port of the web server")
|
||||||
flag.IntVar(&webPort, "p", 8808, "port of the web server (shorthand)")
|
flag.IntVar(&webPort, "p", 8808, "port of the web server (shorthand)")
|
||||||
|
@ -70,6 +51,8 @@ func main() {
|
||||||
flag.StringVar(&colorsFile, "c", "colors.txt", "colors used to render map tiles (shorthand).")
|
flag.StringVar(&colorsFile, "c", "colors.txt", "colors used to render map tiles (shorthand).")
|
||||||
flag.BoolVar(&transparent, "transparent", false, "Render transparent blocks.")
|
flag.BoolVar(&transparent, "transparent", false, "Render transparent blocks.")
|
||||||
flag.BoolVar(&transparent, "t", false, "Render transparent blocks (shorthand).")
|
flag.BoolVar(&transparent, "t", false, "Render transparent blocks (shorthand).")
|
||||||
|
flag.BoolVar(&websockets, "websockets", false, "Forward tile changes to clients via websockets.")
|
||||||
|
flag.BoolVar(&websockets, "ws", false, "Forward tile changes to clients via websockets (shorthand).")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -78,6 +61,15 @@ func main() {
|
||||||
subBaseLine := newSubBaseLine(mapDir)
|
subBaseLine := newSubBaseLine(mapDir)
|
||||||
router.Path("/map/{z:[0-9]+}/{x:[0-9]+}/{y:[0-9]+}.png").Handler(subBaseLine)
|
router.Path("/map/{z:[0-9]+}/{x:[0-9]+}/{y:[0-9]+}.png").Handler(subBaseLine)
|
||||||
|
|
||||||
|
var btu baseTilesUpdates
|
||||||
|
|
||||||
|
if websockets {
|
||||||
|
wsf := newWebsocketForwarder()
|
||||||
|
go wsf.run()
|
||||||
|
router.Path("/ws").Methods("GET").Handler(wsf)
|
||||||
|
btu = wsf
|
||||||
|
}
|
||||||
|
|
||||||
if redisHost != "" {
|
if redisHost != "" {
|
||||||
var colors *common.Colors
|
var colors *common.Colors
|
||||||
var err error
|
var err error
|
||||||
|
@ -92,7 +84,13 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
tu := newTileUpdater(
|
tu := newTileUpdater(
|
||||||
mapDir, redisAddress, allowedUpdateIps, colors, transparent, workers)
|
mapDir,
|
||||||
|
redisAddress,
|
||||||
|
allowedUpdateIps,
|
||||||
|
colors,
|
||||||
|
transparent,
|
||||||
|
workers,
|
||||||
|
btu)
|
||||||
go tu.doUpdates()
|
go tu.doUpdates()
|
||||||
router.Path("/update").Methods("POST").Handler(tu)
|
router.Path("/update").Methods("POST").Handler(tu)
|
||||||
}
|
}
|
||||||
|
|
36
cmd/mtwebmapper/misc.go
Normal file
36
cmd/mtwebmapper/misc.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2014 by Sascha L. Teichmann
|
||||||
|
// Use of this source code is governed by the MIT license
|
||||||
|
// that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ipsFromHosts(hosts string) ([]net.IP, error) {
|
||||||
|
|
||||||
|
ips := []net.IP{}
|
||||||
|
|
||||||
|
if len(hosts) == 0 { // Empty list: allow all hosts.
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, host := range strings.Split(hosts, ";") {
|
||||||
|
hips, err := net.LookupIP(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ips = append(ips, hips...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a < b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
|
@ -24,8 +24,13 @@ import (
|
||||||
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type baseTilesUpdates interface {
|
||||||
|
BaseTilesUpdated(map[xz]bool)
|
||||||
|
}
|
||||||
|
|
||||||
type tileUpdater struct {
|
type tileUpdater struct {
|
||||||
changes map[xz]bool
|
changes map[xz]bool
|
||||||
|
btu baseTilesUpdates
|
||||||
mapDir string
|
mapDir string
|
||||||
redisAddress string
|
redisAddress string
|
||||||
ips []net.IP
|
ips []net.IP
|
||||||
|
@ -67,9 +72,11 @@ func newTileUpdater(
|
||||||
ips []net.IP,
|
ips []net.IP,
|
||||||
colors *common.Colors,
|
colors *common.Colors,
|
||||||
transparent bool,
|
transparent bool,
|
||||||
workers int) *tileUpdater {
|
workers int,
|
||||||
|
btu baseTilesUpdates) *tileUpdater {
|
||||||
|
|
||||||
tu := tileUpdater{
|
tu := tileUpdater{
|
||||||
|
btu: btu,
|
||||||
mapDir: mapDir,
|
mapDir: mapDir,
|
||||||
redisAddress: redisAddress,
|
redisAddress: redisAddress,
|
||||||
ips: ips,
|
ips: ips,
|
||||||
|
@ -166,7 +173,7 @@ func (tu *tileUpdater) doUpdates() {
|
||||||
|
|
||||||
parentJobs := make(map[xz]uint16)
|
parentJobs := make(map[xz]uint16)
|
||||||
|
|
||||||
for c, _ := range changes {
|
for c := range changes {
|
||||||
//log.Printf("job: %+v", c)
|
//log.Printf("job: %+v", c)
|
||||||
jobs <- c
|
jobs <- c
|
||||||
pxz := c.parent()
|
pxz := c.parent()
|
||||||
|
@ -191,14 +198,11 @@ func (tu *tileUpdater) doUpdates() {
|
||||||
done.Wait()
|
done.Wait()
|
||||||
parentJobs = ppJobs
|
parentJobs = ppJobs
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
if tu.btu != nil {
|
||||||
if a < b {
|
tu.btu.BaseTilesUpdated(changes)
|
||||||
return a
|
}
|
||||||
}
|
}
|
||||||
return b
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePyramidTiles(level int, baseDir string, jobs chan xzm, done *sync.WaitGroup) {
|
func updatePyramidTiles(level int, baseDir string, jobs chan xzm, done *sync.WaitGroup) {
|
||||||
|
|
|
@ -37,9 +37,11 @@
|
||||||
<script src="js/leaflet.js"></script>
|
<script src="js/leaflet.js"></script>
|
||||||
<script src="js/Leaflet.Coordinates-0.1.4.min.js"></script>
|
<script src="js/Leaflet.Coordinates-0.1.4.min.js"></script>
|
||||||
<script src="js/easy-button.js"></script>
|
<script src="js/easy-button.js"></script>
|
||||||
|
<script src="js/auto-update.js"></script>
|
||||||
<script type="text/javascript" src="js/leaflet-hash.js"></script>
|
<script type="text/javascript" src="js/leaflet-hash.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
var useWebsocket = false; // Set to true if you want websocket support
|
||||||
|
|
||||||
L.Projection.NoWrap = {
|
L.Projection.NoWrap = {
|
||||||
project: function (latlng) {
|
project: function (latlng) {
|
||||||
|
@ -93,10 +95,21 @@ L.control.coordinates({
|
||||||
useLatLngOrder: true //ordering of labels, default false-> lng-lat
|
useLatLngOrder: true //ordering of labels, default false-> lng-lat
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
|
var manualUpdateControl;
|
||||||
|
if (useWebsocket && 'WebSocket' in window) {
|
||||||
|
L.autoUpdate('autoUpdate', function(pressed) {
|
||||||
|
if (pressed) {
|
||||||
|
manualUpdateControl.getContainer().style = 'visibility: hidden';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
manualUpdateControl.getContainer().style = 'visibility: visible';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
var layersControl = new L.Control.Layers(rasterMaps, overlayMaps, {collapsed: false});
|
var layersControl = new L.Control.Layers(rasterMaps, overlayMaps, {collapsed: false});
|
||||||
map.addControl(layersControl);
|
map.addControl(layersControl);
|
||||||
|
|
||||||
L.easyButton('fa-refresh',
|
manualUpdateControl = L.easyButton('fa-refresh',
|
||||||
function (){
|
function (){
|
||||||
var tiles = document.getElementsByTagName("img");
|
var tiles = document.getElementsByTagName("img");
|
||||||
for (var i = 0; i < tiles.length; i++) {
|
for (var i = 0; i < tiles.length; i++) {
|
||||||
|
@ -114,7 +127,7 @@ L.easyButton('fa-refresh',
|
||||||
//map._resetView(map.getCenter(), map.getZoom(), false);
|
//map._resetView(map.getCenter(), map.getZoom(), false);
|
||||||
},
|
},
|
||||||
'Update view'
|
'Update view'
|
||||||
)
|
);
|
||||||
var hash = new L.Hash(map)
|
var hash = new L.Hash(map)
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
140
cmd/mtwebmapper/web/js/auto-update.js
Normal file
140
cmd/mtwebmapper/web/js/auto-update.js
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
L.Control.AutoUpdate = L.Control.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topleft',
|
||||||
|
label: 'Automatic update'
|
||||||
|
},
|
||||||
|
pressed: true,
|
||||||
|
|
||||||
|
onAdd: function() {
|
||||||
|
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
|
||||||
|
this.link = L.DomUtil.create('a', 'leaflet-bar-part', container);
|
||||||
|
this.iconStart = L.DomUtil.create('i', 'fa fa-play', this.link);
|
||||||
|
this.link.href = '#';
|
||||||
|
L.DomEvent.on(this.link, 'click', this.cbClick, this);
|
||||||
|
return container;
|
||||||
|
},
|
||||||
|
|
||||||
|
cbClick: function (e) {
|
||||||
|
L.DomEvent.stopPropagation(e);
|
||||||
|
this.intendedFunction(this.pressed);
|
||||||
|
if (this.pressed) {
|
||||||
|
this.pressed = false;
|
||||||
|
this.iconStart.setAttribute('class', 'fa fa-pause');
|
||||||
|
this.autoUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.pressed) {
|
||||||
|
this.pressed = true;
|
||||||
|
this.iconStart.setAttribute('class', 'fa fa-play');
|
||||||
|
this.stopUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
intendedFunction: function() {
|
||||||
|
alert('no function selected');
|
||||||
|
},
|
||||||
|
|
||||||
|
stopUpdate: function() {
|
||||||
|
this.socket.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
autoUpdate: function() {
|
||||||
|
this.socket = new WebSocket('ws://' + window.location.host + '/ws');
|
||||||
|
|
||||||
|
this.socket.onmessage = function(evt) {
|
||||||
|
|
||||||
|
var invalidate = function(json) {
|
||||||
|
var invalidateAll = function(x, y, z) { return true; };
|
||||||
|
|
||||||
|
if (!(typeof json === "string")) {
|
||||||
|
return invalidateAll;
|
||||||
|
}
|
||||||
|
var tiles;
|
||||||
|
try {
|
||||||
|
tiles = JSON.parse(json);
|
||||||
|
} catch (err) {
|
||||||
|
return invalidateAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pyramid = new Array(9);
|
||||||
|
var last = new Object();
|
||||||
|
pyramid[8] = last;
|
||||||
|
|
||||||
|
for (var i = 0; i < tiles.length; i++) {
|
||||||
|
var xz = tiles[i];
|
||||||
|
last[xz.X + "#" + xz.Z] = xz;
|
||||||
|
}
|
||||||
|
for (var p = 7; p >= 0; p--) {
|
||||||
|
var prev = pyramid[p+1];
|
||||||
|
var curr = new Object();
|
||||||
|
pyramid[p] = curr;
|
||||||
|
for (var k in prev) {
|
||||||
|
if (prev.hasOwnProperty(k)) {
|
||||||
|
var oxz = prev[k];
|
||||||
|
var nxz = { X: oxz.X >> 1, Z: oxz.Z >> 1 };
|
||||||
|
curr[nxz.X + "#" + nxz.Z] = nxz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return function(x, y, z) {
|
||||||
|
if (y > 8) {
|
||||||
|
x >>= y - 8;
|
||||||
|
z >>= y - 8;
|
||||||
|
y = 8;
|
||||||
|
}
|
||||||
|
var level = pyramid[y];
|
||||||
|
var k = x + "#" + z;
|
||||||
|
return level.hasOwnProperty(k);
|
||||||
|
};
|
||||||
|
} (evt.data);
|
||||||
|
|
||||||
|
var tiles = document.getElementsByTagName('img');
|
||||||
|
var re = /\/map\/([0-9]+)\/([0-9]+)\/([0-9]+).*/;
|
||||||
|
for (var i = 0; i < tiles.length; i++) {
|
||||||
|
var img = tiles[i];
|
||||||
|
var cl = img.getAttribute('class');
|
||||||
|
if (!cl.contains('leaflet-tile-loaded')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var src = img.src;
|
||||||
|
var coord = src.match(re);
|
||||||
|
if (coord == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var y = parseInt(coord[1]);
|
||||||
|
var x = parseInt(coord[2]);
|
||||||
|
var z = parseInt(coord[3]);
|
||||||
|
if (invalidate(x, y, z)) {
|
||||||
|
var idx = src.lastIndexOf('#');
|
||||||
|
if (idx >= 0) {
|
||||||
|
src = src.substring(0, idx);
|
||||||
|
}
|
||||||
|
img.src = src + '#' + Math.random();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.autoUpdate = function(cbLabel, cbFunc, cbMap) {
|
||||||
|
var control = new L.Control.AutoUpdate();
|
||||||
|
if (cbLabel) {
|
||||||
|
control.options.label = cbLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cbFunc) {
|
||||||
|
control.intendedFunction = cbFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cbMap === '') {
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
else if (cbMap) {
|
||||||
|
cbMap.addControl(control);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
map.addControl(control);
|
||||||
|
}
|
||||||
|
return control;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user