mirror of
https://bitbucket.org/s_l_teichmann/mtsatellite
synced 2024-12-23 16:50:18 +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 \
|
||||
-web=/path/to/your/static/web \
|
||||
-redis-host=localhost \
|
||||
-workers=2
|
||||
-workers=2 \
|
||||
-websockets=false
|
||||
|
||||
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
|
||||
@ -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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
||||
|
||||
"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() {
|
||||
var (
|
||||
webPort int
|
||||
@ -48,6 +28,7 @@ func main() {
|
||||
workers int
|
||||
transparent bool
|
||||
updateHosts string
|
||||
websockets bool
|
||||
)
|
||||
flag.IntVar(&webPort, "web-port", 8808, "port of the web server")
|
||||
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.BoolVar(&transparent, "transparent", false, "Render transparent blocks.")
|
||||
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()
|
||||
|
||||
@ -78,6 +61,15 @@ func main() {
|
||||
subBaseLine := newSubBaseLine(mapDir)
|
||||
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 != "" {
|
||||
var colors *common.Colors
|
||||
var err error
|
||||
@ -92,7 +84,13 @@ func main() {
|
||||
}
|
||||
|
||||
tu := newTileUpdater(
|
||||
mapDir, redisAddress, allowedUpdateIps, colors, transparent, workers)
|
||||
mapDir,
|
||||
redisAddress,
|
||||
allowedUpdateIps,
|
||||
colors,
|
||||
transparent,
|
||||
workers,
|
||||
btu)
|
||||
go tu.doUpdates()
|
||||
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"
|
||||
)
|
||||
|
||||
type baseTilesUpdates interface {
|
||||
BaseTilesUpdated(map[xz]bool)
|
||||
}
|
||||
|
||||
type tileUpdater struct {
|
||||
changes map[xz]bool
|
||||
btu baseTilesUpdates
|
||||
mapDir string
|
||||
redisAddress string
|
||||
ips []net.IP
|
||||
@ -67,9 +72,11 @@ func newTileUpdater(
|
||||
ips []net.IP,
|
||||
colors *common.Colors,
|
||||
transparent bool,
|
||||
workers int) *tileUpdater {
|
||||
workers int,
|
||||
btu baseTilesUpdates) *tileUpdater {
|
||||
|
||||
tu := tileUpdater{
|
||||
btu: btu,
|
||||
mapDir: mapDir,
|
||||
redisAddress: redisAddress,
|
||||
ips: ips,
|
||||
@ -166,7 +173,7 @@ func (tu *tileUpdater) doUpdates() {
|
||||
|
||||
parentJobs := make(map[xz]uint16)
|
||||
|
||||
for c, _ := range changes {
|
||||
for c := range changes {
|
||||
//log.Printf("job: %+v", c)
|
||||
jobs <- c
|
||||
pxz := c.parent()
|
||||
@ -191,14 +198,11 @@ func (tu *tileUpdater) doUpdates() {
|
||||
done.Wait()
|
||||
parentJobs = ppJobs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
if tu.btu != nil {
|
||||
tu.btu.BaseTilesUpdated(changes)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
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.Coordinates-0.1.4.min.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>
|
||||
|
||||
var useWebsocket = false; // Set to true if you want websocket support
|
||||
|
||||
L.Projection.NoWrap = {
|
||||
project: function (latlng) {
|
||||
@ -93,10 +95,21 @@ L.control.coordinates({
|
||||
useLatLngOrder: true //ordering of labels, default false-> lng-lat
|
||||
}).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});
|
||||
map.addControl(layersControl);
|
||||
|
||||
L.easyButton('fa-refresh',
|
||||
manualUpdateControl = L.easyButton('fa-refresh',
|
||||
function (){
|
||||
var tiles = document.getElementsByTagName("img");
|
||||
for (var i = 0; i < tiles.length; i++) {
|
||||
@ -114,7 +127,7 @@ L.easyButton('fa-refresh',
|
||||
//map._resetView(map.getCenter(), map.getZoom(), false);
|
||||
},
|
||||
'Update view'
|
||||
)
|
||||
);
|
||||
var hash = new L.Hash(map)
|
||||
</script>
|
||||
</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