Remplissage du dépôt
125
cmd/mtwebmapper/forwardupdates.go
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright 2014, 2015 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 msg
|
||||
connections map[*connection]bool
|
||||
}
|
||||
|
||||
type connection struct {
|
||||
ws *websocket.Conn
|
||||
send chan []byte
|
||||
}
|
||||
|
||||
type msg struct {
|
||||
tiles []xz
|
||||
pls []*player
|
||||
}
|
||||
|
||||
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 msg),
|
||||
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 message := <-wsf.broadcast:
|
||||
if len(wsf.connections) == 0 {
|
||||
continue
|
||||
}
|
||||
encMsg := map[string]interface{}{}
|
||||
|
||||
if message.tiles != nil {
|
||||
encMsg["tiles"] = message.tiles
|
||||
}
|
||||
|
||||
if message.pls != nil {
|
||||
encMsg["players"] = message.pls
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
encoder := json.NewEncoder(&buf)
|
||||
if err := encoder.Encode(encMsg); err != nil {
|
||||
log.Printf("encoding changes failed: %s\n", err)
|
||||
continue
|
||||
}
|
||||
m := buf.Bytes()
|
||||
for c := range wsf.connections {
|
||||
select {
|
||||
case c.send <- m:
|
||||
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 []xz) {
|
||||
wsf.broadcast <- msg{tiles: changes}
|
||||
}
|
||||
|
||||
func (wsf *websocketForwarder) BroadcastPlayers(pls []*player) {
|
||||
wsf.broadcast <- msg{pls: pls}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
149
cmd/mtwebmapper/main.go
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright 2014, 2015 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 (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
webPort int
|
||||
webHost string
|
||||
webDir string
|
||||
mapDir string
|
||||
redisPort int
|
||||
redisHost string
|
||||
colorsFile string
|
||||
bgColor string
|
||||
workers int
|
||||
transparent bool
|
||||
transparentDim float64
|
||||
updateHosts string
|
||||
websockets bool
|
||||
playersFIFO string
|
||||
version bool
|
||||
yMin int
|
||||
yMax int
|
||||
)
|
||||
|
||||
defaultBgColor := common.ColorToHex(common.BackgroundColor)
|
||||
|
||||
flag.IntVar(&webPort, "web-port", 8808, "port of the web server")
|
||||
flag.IntVar(&webPort, "p", 8808, "port of the web server (shorthand)")
|
||||
flag.StringVar(&webHost, "web-host", "localhost", "address to bind web server")
|
||||
flag.StringVar(&webHost, "h", "localhost", "address to bind web server(shorthand)")
|
||||
flag.StringVar(&webDir, "web", "web", "static served web files.")
|
||||
flag.StringVar(&webDir, "w", "web", "static served web files (shorthand)")
|
||||
flag.StringVar(&mapDir, "map", "map", "directory of prerendered tiles")
|
||||
flag.StringVar(&mapDir, "m", "map", "directory of prerendered tiles (shorthand)")
|
||||
flag.StringVar(&updateHosts, "update-hosts", "localhost",
|
||||
"';' separated list of hosts which are allowed to send map update requests")
|
||||
flag.StringVar(&updateHosts, "u", "localhost",
|
||||
"';' separated list of hosts which are allowed to send map update requests (shorthand)")
|
||||
flag.StringVar(&redisHost, "redis-host", "", "address of the backend Redis server")
|
||||
flag.StringVar(&redisHost, "rh", "", "address of the backend Redis server (shorthand)")
|
||||
flag.IntVar(&redisPort, "redis-port", 6379, "port of the backend Redis server")
|
||||
flag.IntVar(&redisPort, "rp", 6379, "port of the backend Redis server (shorthand)")
|
||||
flag.IntVar(&workers, "workers", 1, "number of workers to render tiles")
|
||||
flag.StringVar(&colorsFile, "colors", "colors.txt", "colors used to render map tiles.")
|
||||
flag.StringVar(&colorsFile, "c", "colors.txt", "colors used to render map tiles (shorthand).")
|
||||
flag.StringVar(&bgColor, "background", defaultBgColor, "background color")
|
||||
flag.StringVar(&bgColor, "bg", defaultBgColor, "background color (shorthand)")
|
||||
flag.BoolVar(&transparent, "transparent", false, "Render transparent blocks.")
|
||||
flag.BoolVar(&transparent, "t", false, "Render transparent blocks (shorthand).")
|
||||
flag.Float64Var(&transparentDim,
|
||||
"transparent-dim", common.DefaultTransparentDim*100.0,
|
||||
"Extra dimming of transparent nodes each depth meter in percent.")
|
||||
flag.Float64Var(&transparentDim,
|
||||
"td", common.DefaultTransparentDim*100.0,
|
||||
"Extra fimming of transparent nodes each depth meter in percent. (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.StringVar(&playersFIFO, "players", "", "Path to FIFO file to read active players from.")
|
||||
flag.StringVar(&playersFIFO, "ps", "", "Path to FIFO file to read active players from (shorthand).")
|
||||
flag.IntVar(&yMin, "ymin", common.MinHeight, "Minimum y in blocks.")
|
||||
flag.IntVar(&yMax, "ymax", common.MaxHeight, "Maximum y in blocks.")
|
||||
flag.BoolVar(&version, "version", false, "Print version and exit.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if version {
|
||||
common.PrintVersionAndExit()
|
||||
}
|
||||
|
||||
bg := common.ParseColorDefault(bgColor, common.BackgroundColor)
|
||||
|
||||
router := mux.NewRouter()
|
||||
|
||||
subBaseLine := newSubBaseLine(mapDir, bg)
|
||||
router.Path("/map/{z:[0-9]+}/{x:[0-9]+}/{y:[0-9]+}.png").Handler(subBaseLine)
|
||||
|
||||
var btu baseTilesUpdates
|
||||
var wsf *websocketForwarder
|
||||
|
||||
if websockets {
|
||||
wsf = newWebsocketForwarder()
|
||||
go wsf.run()
|
||||
router.Path("/ws").Methods("GET").Handler(wsf)
|
||||
btu = wsf
|
||||
}
|
||||
|
||||
if playersFIFO != "" {
|
||||
plys := newPlayers(playersFIFO, wsf)
|
||||
go plys.run()
|
||||
router.Path("/players").Methods("GET").Handler(plys)
|
||||
}
|
||||
|
||||
if redisHost != "" {
|
||||
var colors *common.Colors
|
||||
var err error
|
||||
if colors, err = common.ParseColors(colorsFile); err != nil {
|
||||
log.Fatalf("ERROR: problem loading colors: %s", err)
|
||||
}
|
||||
colors.TransparentDim = common.Clamp32f(
|
||||
float32(transparentDim/100.0), 0.0, 100.0)
|
||||
var redisAddress string
|
||||
if strings.ContainsRune(redisHost, '/') {
|
||||
redisAddress = redisHost
|
||||
} else {
|
||||
redisAddress = fmt.Sprintf("%s:%d", redisHost, redisPort)
|
||||
}
|
||||
|
||||
var allowedUpdateIps []net.IP
|
||||
if allowedUpdateIps, err = ipsFromHosts(updateHosts); err != nil {
|
||||
log.Fatalf("ERROR: name resolving problem: %s", err)
|
||||
}
|
||||
|
||||
tu := newTileUpdater(
|
||||
mapDir,
|
||||
redisAddress,
|
||||
allowedUpdateIps,
|
||||
colors, bg,
|
||||
yMin, yMax,
|
||||
transparent,
|
||||
workers,
|
||||
btu)
|
||||
go tu.doUpdates()
|
||||
router.Path("/update").Methods("POST").Handler(tu)
|
||||
}
|
||||
|
||||
router.PathPrefix("/").Handler(http.FileServer(http.Dir(webDir)))
|
||||
http.Handle("/", router)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", webHost, webPort)
|
||||
if err := http.ListenAndServe(addr, nil); err != nil {
|
||||
log.Fatalf("Starting server failed: %s\n", err)
|
||||
}
|
||||
}
|
29
cmd/mtwebmapper/misc.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2014, 2015 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
|
||||
}
|
148
cmd/mtwebmapper/players.go
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2014, 2015 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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const sleepInterval = time.Second * 5
|
||||
|
||||
var geoJSONTmpl = template.Must(template.New("geojson").Parse(
|
||||
`{ "type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [{{.Z}}, {{.X}}]
|
||||
},
|
||||
"properties": {
|
||||
"name": "{{.Name | html }}"
|
||||
}
|
||||
}`))
|
||||
|
||||
type player struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
Z float64 `json:"z"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type players struct {
|
||||
fifo string
|
||||
wsf *websocketForwarder
|
||||
pls []*player
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func newPlayers(fifo string, wsf *websocketForwarder) *players {
|
||||
return &players{fifo: fifo, wsf: wsf, pls: []*player{}}
|
||||
}
|
||||
|
||||
func (p *player) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := geoJSONTmpl.Execute(&buf, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (p *player) same(o *player) bool {
|
||||
return p.Name == o.Name &&
|
||||
math.Abs(p.X-o.X) < 0.000001 &&
|
||||
math.Abs(p.Y-o.Y) < 0.000001 &&
|
||||
math.Abs(p.Z-o.Z) < 0.000001
|
||||
}
|
||||
|
||||
type sortPlayersByName []*player
|
||||
|
||||
func (pls sortPlayersByName) Len() int {
|
||||
return len(pls)
|
||||
}
|
||||
|
||||
func (pls sortPlayersByName) Less(i, j int) bool {
|
||||
return pls[i].Name < pls[j].Name
|
||||
}
|
||||
|
||||
func (pls sortPlayersByName) Swap(i, j int) {
|
||||
pls[i], pls[j] = pls[j], pls[i]
|
||||
}
|
||||
|
||||
func (ps *players) readFromFIFO() ([]*player, error) {
|
||||
file, err := os.Open(ps.fifo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
reader := bufio.NewReader(file)
|
||||
decoder := json.NewDecoder(reader)
|
||||
var pls []*player
|
||||
|
||||
if err = decoder.Decode(&pls); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pls, nil
|
||||
}
|
||||
|
||||
func samePlayers(a, b []*player) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, p := range a {
|
||||
if !p.same(b[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ps *players) run() {
|
||||
for {
|
||||
pls, err := ps.readFromFIFO()
|
||||
if err != nil {
|
||||
//log.Printf("err: %s\n", err)
|
||||
time.Sleep(sleepInterval)
|
||||
continue
|
||||
}
|
||||
if pls == nil {
|
||||
//log.Println("no players")
|
||||
continue
|
||||
}
|
||||
//log.Printf("%+q\n", pls)
|
||||
sort.Sort(sortPlayersByName(pls))
|
||||
var change bool
|
||||
ps.mu.Lock()
|
||||
//log.Printf("%+q\n", pls)
|
||||
//log.Printf("%+q\n", ps.pls)
|
||||
if change = !samePlayers(pls, ps.pls); change {
|
||||
ps.pls = pls
|
||||
}
|
||||
ps.mu.Unlock()
|
||||
if change && ps.wsf != nil {
|
||||
// TODO: Throttle this!
|
||||
ps.wsf.BroadcastPlayers(pls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *players) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Set("Content-Type", "application/json")
|
||||
var pls []*player
|
||||
ps.mu.RLock()
|
||||
pls = ps.pls
|
||||
ps.mu.RUnlock()
|
||||
encoder := json.NewEncoder(rw)
|
||||
if err := encoder.Encode(pls); err != nil {
|
||||
log.Printf("error: sending JSON failed: %s\n", err)
|
||||
}
|
||||
}
|
227
cmd/mtwebmapper/subbaseline.go
Normal file
@ -0,0 +1,227 @@
|
||||
// Copyright 2014, 2015 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 (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type subBaseLine struct {
|
||||
mapDir string
|
||||
bg color.RGBA
|
||||
}
|
||||
|
||||
func newSubBaseLine(mapDir string, bg color.RGBA) *subBaseLine {
|
||||
return &subBaseLine{mapDir: mapDir, bg: bg}
|
||||
}
|
||||
|
||||
func (sb *subBaseLine) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rw.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
xs := vars["x"]
|
||||
ys := vars["y"]
|
||||
zs := vars["z"]
|
||||
|
||||
x, y, z := toUint(xs), toUint(ys), toUint(zs)
|
||||
if z < 9 {
|
||||
http.ServeFile(rw, r, filepath.Join(sb.mapDir,
|
||||
strconv.Itoa(int(z)),
|
||||
strconv.Itoa(int(x)),
|
||||
strconv.Itoa(int(y))+".png"))
|
||||
return
|
||||
}
|
||||
|
||||
if z > 16 {
|
||||
z = 16
|
||||
}
|
||||
tx := x >> (z - 8)
|
||||
ty := y >> (z - 8)
|
||||
|
||||
baseTile := filepath.Join(
|
||||
sb.mapDir,
|
||||
"8",
|
||||
strconv.Itoa(int(tx)),
|
||||
strconv.Itoa(int(ty))+".png")
|
||||
|
||||
var err error
|
||||
var fi os.FileInfo
|
||||
if fi, err = os.Stat(baseTile); err != nil {
|
||||
http.NotFound(rw, r)
|
||||
return
|
||||
}
|
||||
|
||||
if checkLastModified(rw, r, fi.ModTime()) || checkETag(rw, r, fi) {
|
||||
return
|
||||
}
|
||||
|
||||
rx := x & ^(^uint(0) << (z - 8))
|
||||
ry := y & ^(^uint(0) << (z - 8))
|
||||
|
||||
parts := uint(1) << (z - 8)
|
||||
|
||||
w := uint(256) / parts
|
||||
xo := w * rx
|
||||
yo := w * (parts - 1 - ry)
|
||||
|
||||
img := common.LoadPNG(baseTile, sb.bg)
|
||||
|
||||
type subImage interface {
|
||||
SubImage(image.Rectangle) image.Image
|
||||
}
|
||||
|
||||
if si, ok := img.(subImage); ok {
|
||||
img = si.SubImage(image.Rect(int(xo), int(yo), int(xo+w), int(yo+w)))
|
||||
} else {
|
||||
// Should not happen.
|
||||
http.Error(rw,
|
||||
http.StatusText(http.StatusInternalServerError),
|
||||
http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
img = blowUp(img)
|
||||
|
||||
rw.Header().Set("Content-Type", "image/png")
|
||||
if err = png.Encode(rw, img); err != nil {
|
||||
log.Printf("WARN: encoding image failed: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func blowUp(src image.Image) *image.RGBA {
|
||||
|
||||
// Fast path for RGBA -> RGBA
|
||||
if rgba, ok := src.(*image.RGBA); ok {
|
||||
return blowUpRGBA(rgba)
|
||||
}
|
||||
|
||||
// Fallback
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 256, 256))
|
||||
|
||||
// fix point numbers x:8
|
||||
dx, dy := src.Bounds().Dx(), src.Bounds().Dy()
|
||||
|
||||
bx, by := src.Bounds().Min.X<<8, src.Bounds().Min.Y<<8
|
||||
|
||||
//start := time.Now()
|
||||
|
||||
pix := dst.Pix
|
||||
|
||||
lineOfs := dst.PixOffset(0, 0) // Should be 0.
|
||||
|
||||
py := by
|
||||
var r, g, b, a uint8
|
||||
for y := 0; y < 256; y++ {
|
||||
sy := (py >> 8) & 0xff
|
||||
ox := -1
|
||||
px := bx
|
||||
ofs := lineOfs // Should not really b needed
|
||||
lineOfs += dst.Stride
|
||||
for x := 0; x < 256; x++ {
|
||||
sx := (px >> 8) & 0xff
|
||||
if sx != ox { // Minimize interface indirection access.
|
||||
ox = sx
|
||||
xr, xg, xb, xa := src.At(sx, sy).RGBA()
|
||||
r, g, b, a = uint8(xr), uint8(xg), uint8(xb), uint8(xa)
|
||||
}
|
||||
pix[ofs] = r
|
||||
pix[ofs+1] = g
|
||||
pix[ofs+2] = b
|
||||
pix[ofs+3] = a
|
||||
ofs += 4
|
||||
px += dx
|
||||
}
|
||||
py += dy
|
||||
}
|
||||
|
||||
//log.Printf("Rendering took: %s\n", time.Since(start))
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func blowUpRGBA(src *image.RGBA) *image.RGBA {
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 256, 256))
|
||||
|
||||
// fix point numbers x:8
|
||||
dx, dy := src.Bounds().Dx(), src.Bounds().Dy()
|
||||
|
||||
bx, by := src.Bounds().Min.X<<8, src.Bounds().Min.Y<<8
|
||||
|
||||
//start := time.Now()
|
||||
|
||||
sPix := src.Pix
|
||||
dPix := dst.Pix
|
||||
|
||||
py := by
|
||||
// Assuming memory layout is packed 256*256*4 with stride of 4*256.
|
||||
// for dLineOfs, dEnd := dst.PixOffset(0, 0), dst.PixOffset(0, 256); dLineOfs < dEnd; dLineOfs += dst.Stride {
|
||||
for ofs := 0; ofs < 256*256*4; {
|
||||
sy := (py >> 8) & 0xff
|
||||
sLineOfs := src.PixOffset(0, sy)
|
||||
px := bx
|
||||
// ofs := dLineOfs
|
||||
for end := ofs + 4*256; ofs < end; ofs += 4 {
|
||||
sOfs := sLineOfs + ((px >> 6) & 0x3fc)
|
||||
px += dx
|
||||
dPix[ofs] = sPix[sOfs]
|
||||
dPix[ofs+1] = sPix[sOfs+1]
|
||||
dPix[ofs+2] = sPix[sOfs+2]
|
||||
dPix[ofs+3] = sPix[sOfs+3]
|
||||
}
|
||||
py += dy
|
||||
}
|
||||
|
||||
//log.Printf("Rendering took: %s\n", time.Since(start))
|
||||
return dst
|
||||
}
|
||||
|
||||
func checkETag(w http.ResponseWriter, r *http.Request, fi os.FileInfo) bool {
|
||||
etag := fmt.Sprintf("%x-%x", fi.ModTime().Unix(), fi.Size())
|
||||
if ifNoneMatch := r.Header.Get("If-None-Match"); ifNoneMatch == etag {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return true
|
||||
}
|
||||
w.Header().Set("ETag", etag)
|
||||
return false
|
||||
}
|
||||
|
||||
func checkLastModified(w http.ResponseWriter, r *http.Request, modtime time.Time) bool {
|
||||
|
||||
if modtime.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
// The Date-Modified header truncates sub-second precision, so
|
||||
// use mtime < t+1s instead of mtime <= t to check for unmodified.
|
||||
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return true
|
||||
}
|
||||
w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat))
|
||||
return false
|
||||
}
|
||||
|
||||
func toUint(s string) uint {
|
||||
x, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Cannot convert to int: %s\n", err)
|
||||
return 0
|
||||
}
|
||||
return uint(x)
|
||||
}
|
379
cmd/mtwebmapper/tilesupdater.go
Normal file
@ -0,0 +1,379 @@
|
||||
// Copyright 2014, 2015 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 (
|
||||
"encoding/json"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bamiaux/rez"
|
||||
|
||||
"bytes"
|
||||
|
||||
"bitbucket.org/s_l_teichmann/mtsatellite/common"
|
||||
)
|
||||
|
||||
// Number of check sums to keep in memory.
|
||||
const maxHashedTiles = 256
|
||||
|
||||
type baseTilesUpdates interface {
|
||||
BaseTilesUpdated([]xz)
|
||||
}
|
||||
|
||||
type tileUpdater struct {
|
||||
changes map[xz]struct{}
|
||||
btu baseTilesUpdates
|
||||
mapDir string
|
||||
redisAddress string
|
||||
ips []net.IP
|
||||
colors *common.Colors
|
||||
bg color.RGBA
|
||||
yMin, yMax int16
|
||||
workers int
|
||||
transparent bool
|
||||
cond *sync.Cond
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
type xz struct {
|
||||
X int16
|
||||
Z int16
|
||||
}
|
||||
|
||||
type xzc struct {
|
||||
xz
|
||||
canceled bool
|
||||
}
|
||||
|
||||
type xzm struct {
|
||||
xz
|
||||
Mask uint16
|
||||
}
|
||||
|
||||
func (c xz) quantize() xz {
|
||||
return xz{X: (c.X - -1933) / 16, Z: (c.Z - -1933) / 16}
|
||||
}
|
||||
|
||||
func (c xz) dequantize() xz {
|
||||
return xz{X: c.X*16 + -1933, Z: c.Z*16 + -1933}
|
||||
}
|
||||
|
||||
func (c xz) parent() xzm {
|
||||
xp, xr := c.X>>1, uint16(c.X&1)
|
||||
zp, zr := c.Z>>1, uint16(c.Z&1)
|
||||
return xzm{
|
||||
xz{X: xp, Z: zp},
|
||||
1 << (zr<<1 | xr)}
|
||||
}
|
||||
|
||||
func newTileUpdater(
|
||||
mapDir, redisAddress string,
|
||||
ips []net.IP,
|
||||
colors *common.Colors,
|
||||
bg color.RGBA,
|
||||
yMin, yMax int,
|
||||
transparent bool,
|
||||
workers int,
|
||||
btu baseTilesUpdates) *tileUpdater {
|
||||
|
||||
tu := tileUpdater{
|
||||
btu: btu,
|
||||
mapDir: mapDir,
|
||||
redisAddress: redisAddress,
|
||||
ips: ips,
|
||||
changes: map[xz]struct{}{},
|
||||
colors: colors,
|
||||
bg: bg,
|
||||
yMin: int16(yMin),
|
||||
yMax: int16(yMax),
|
||||
transparent: transparent,
|
||||
workers: workers}
|
||||
tu.cond = sync.NewCond(&tu.mu)
|
||||
return &tu
|
||||
}
|
||||
|
||||
func (tu *tileUpdater) checkIP(r *http.Request) bool {
|
||||
if len(tu.ips) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
idx := strings.LastIndex(r.RemoteAddr, ":")
|
||||
if idx < 0 {
|
||||
log.Printf("WARN: cannot extract host from '%s'.\n", r.RemoteAddr)
|
||||
return false
|
||||
}
|
||||
|
||||
host := strings.Trim(r.RemoteAddr[:idx], "[]")
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
log.Printf("WARN: cannot get IP for host '%s'.\n", host)
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range tu.ips {
|
||||
if bytes.Compare(tu.ips[i], ip) == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (tu *tileUpdater) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
if !tu.checkIP(r) {
|
||||
log.Printf("WARN: Unauthorized update request from '%s'\n", r.RemoteAddr)
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
var newChanges []xz
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err = decoder.Decode(&newChanges); err != nil {
|
||||
log.Printf("WARN: JSON document broken: %s\n", err)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if len(newChanges) > 0 {
|
||||
tu.cond.L.Lock()
|
||||
for _, c := range newChanges {
|
||||
tu.changes[c.quantize()] = struct{}{}
|
||||
}
|
||||
tu.cond.L.Unlock()
|
||||
tu.cond.Signal()
|
||||
}
|
||||
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func extractChanges(changes map[xz]struct{}) []xzc {
|
||||
chs := make([]xzc, len(changes))
|
||||
var i int
|
||||
for ch := range changes {
|
||||
chs[i] = xzc{ch, false}
|
||||
i++
|
||||
}
|
||||
return chs
|
||||
}
|
||||
|
||||
func activeChanges(changes []xzc) []xz {
|
||||
chs := make([]xz, 0, len(changes))
|
||||
for i := range changes {
|
||||
if !changes[i].canceled {
|
||||
chs = append(chs, changes[i].xz)
|
||||
}
|
||||
}
|
||||
return chs
|
||||
}
|
||||
|
||||
func (tu *tileUpdater) doUpdates() {
|
||||
|
||||
bth := common.NewBaseTileHash(maxHashedTiles)
|
||||
|
||||
baseDir := filepath.Join(tu.mapDir, "8")
|
||||
|
||||
for {
|
||||
tu.cond.L.Lock()
|
||||
for len(tu.changes) == 0 {
|
||||
tu.cond.Wait()
|
||||
}
|
||||
changes := extractChanges(tu.changes)
|
||||
tu.changes = map[xz]struct{}{}
|
||||
tu.cond.L.Unlock()
|
||||
|
||||
jobs := make(chan *xzc)
|
||||
var done sync.WaitGroup
|
||||
|
||||
var proto string
|
||||
if strings.ContainsRune(tu.redisAddress, '/') {
|
||||
proto = "unix"
|
||||
} else {
|
||||
proto = "tcp"
|
||||
}
|
||||
|
||||
for i, n := 0, common.Min(tu.workers, len(changes)); i < n; i++ {
|
||||
var client *common.RedisClient
|
||||
var err error
|
||||
if client, err = common.NewRedisClient(proto, tu.redisAddress); err != nil {
|
||||
log.Printf("WARN: Cannot connect to redis server: %s\n", err)
|
||||
continue
|
||||
}
|
||||
btc := common.NewBaseTileCreator(
|
||||
client, tu.colors, tu.bg,
|
||||
tu.yMin, tu.yMax,
|
||||
tu.transparent, baseDir)
|
||||
done.Add(1)
|
||||
go tu.updateBaseTiles(jobs, btc, &done, bth.Update)
|
||||
}
|
||||
|
||||
for i := range changes {
|
||||
jobs <- &changes[i]
|
||||
}
|
||||
close(jobs)
|
||||
done.Wait()
|
||||
|
||||
actChs := activeChanges(changes)
|
||||
|
||||
if len(actChs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
parentJobs := make(map[xz]uint16)
|
||||
for i := range actChs {
|
||||
pxz := actChs[i].parent()
|
||||
parentJobs[pxz.xz] |= pxz.Mask
|
||||
}
|
||||
|
||||
for level := 7; level >= 0; level-- {
|
||||
pJobs := make(chan xzm)
|
||||
for i, n := 0, common.Min(len(parentJobs), tu.workers); i < n; i++ {
|
||||
done.Add(1)
|
||||
go tu.updatePyramidTiles(level, pJobs, &done)
|
||||
}
|
||||
ppJobs := make(map[xz]uint16)
|
||||
for c, mask := range parentJobs {
|
||||
pJobs <- xzm{c, mask}
|
||||
pxz := c.parent()
|
||||
ppJobs[pxz.xz] |= pxz.Mask
|
||||
}
|
||||
close(pJobs)
|
||||
done.Wait()
|
||||
parentJobs = ppJobs
|
||||
}
|
||||
|
||||
if tu.btu != nil {
|
||||
tu.btu.BaseTilesUpdated(actChs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tu *tileUpdater) updatePyramidTiles(
|
||||
level int, jobs chan xzm, done *sync.WaitGroup) {
|
||||
|
||||
defer done.Done()
|
||||
scratch := image.NewRGBA(image.Rect(0, 0, 256, 256))
|
||||
resized := image.NewRGBA(image.Rect(0, 0, 128, 128))
|
||||
|
||||
for job := range jobs {
|
||||
if err := tu.updatePyramidTile(scratch, resized, level, job); err != nil {
|
||||
log.Printf("Updating pyramid tile failed: %s\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
(0,0) (128, 0)
|
||||
(0, 128) (128, 128)
|
||||
*/
|
||||
|
||||
var dps = [4]image.Point{
|
||||
image.Pt(0, 128),
|
||||
image.Pt(128, 128),
|
||||
image.Pt(0, 0),
|
||||
image.Pt(128, 0),
|
||||
}
|
||||
|
||||
var ofs = [4][2]int{
|
||||
{0, 0},
|
||||
{1, 0},
|
||||
{0, 1},
|
||||
{1, 1}}
|
||||
|
||||
var windowSize = image.Pt(128, 128)
|
||||
|
||||
func (tu *tileUpdater) updatePyramidTile(scratch, resized *image.RGBA, level int, j xzm) error {
|
||||
|
||||
var orig image.Image
|
||||
|
||||
origPath := filepath.Join(
|
||||
tu.mapDir,
|
||||
strconv.Itoa(level),
|
||||
strconv.Itoa(int(j.X)),
|
||||
strconv.Itoa(int(j.Z))+".png")
|
||||
|
||||
sr := resized.Bounds()
|
||||
levelDir := strconv.Itoa(level + 1)
|
||||
for i := uint16(0); i < 4; i++ {
|
||||
if j.Mask&(1<<i) != 0 {
|
||||
//log.Printf("level %d: modified %d\n", level, i)
|
||||
o := ofs[i]
|
||||
bx, bz := int(2*j.X), int(2*j.Z)
|
||||
path := filepath.Join(
|
||||
tu.mapDir,
|
||||
levelDir,
|
||||
strconv.Itoa(bx+o[0]),
|
||||
strconv.Itoa(bz+o[1])+".png")
|
||||
img := common.LoadPNG(path, tu.bg)
|
||||
if err := rez.Convert(resized, img, common.ResizeFilter); err != nil {
|
||||
return err
|
||||
}
|
||||
r := sr.Sub(sr.Min).Add(dps[i])
|
||||
draw.Draw(scratch, r, resized, sr.Min, draw.Src)
|
||||
} else {
|
||||
// Load lazy
|
||||
if orig == nil {
|
||||
orig = common.LoadPNG(origPath, tu.bg)
|
||||
}
|
||||
//log.Printf("level %d: copied %d\n", level, i)
|
||||
min := orig.Bounds().Min.Add(dps[i])
|
||||
r := image.Rectangle{min, min.Add(windowSize)}
|
||||
draw.Draw(scratch, r, orig, min, draw.Src)
|
||||
}
|
||||
}
|
||||
|
||||
return common.SaveAsPNGAtomic(origPath, scratch)
|
||||
}
|
||||
|
||||
func (tu *tileUpdater) updateBaseTiles(
|
||||
jobs chan *xzc,
|
||||
btc *common.BaseTileCreator,
|
||||
done *sync.WaitGroup,
|
||||
update common.BaseTileUpdateFunc) {
|
||||
|
||||
type jobWriter struct {
|
||||
job *xzc
|
||||
wFn func() (bool, error)
|
||||
}
|
||||
|
||||
jWs := make(chan jobWriter)
|
||||
|
||||
go func() {
|
||||
for jw := range jWs {
|
||||
updated, err := jw.wFn()
|
||||
if err != nil {
|
||||
log.Printf("WARN: writing tile failed: %v.\n", err)
|
||||
}
|
||||
if !updated {
|
||||
jw.job.canceled = true
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
close(jWs)
|
||||
btc.Close()
|
||||
done.Done()
|
||||
}()
|
||||
|
||||
for job := range jobs {
|
||||
xz := job.dequantize()
|
||||
if err := btc.RenderArea(xz.X-1, xz.Z-1); err != nil {
|
||||
log.Printf("WARN: rendering tile failed: %v.\n", err)
|
||||
job.canceled = true
|
||||
continue
|
||||
}
|
||||
jWs <- jobWriter{job, btc.WriteFunc(int(job.X), int(job.Z), update)}
|
||||
}
|
||||
}
|
1
cmd/mtwebmapper/web/css/Leaflet.Coordinates-0.1.4.css
Normal file
@ -0,0 +1 @@
|
||||
.leaflet-control-coordinates{background-color:#D8D8D8;background-color:rgba(255,255,255,.8);cursor:pointer}.leaflet-control-coordinates,.leaflet-control-coordinates .uiElement input{-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.leaflet-control-coordinates .uiElement{margin:4px}.leaflet-control-coordinates .uiElement .labelFirst{margin-right:4px}.leaflet-control-coordinates .uiHidden{display:none}
|
1338
cmd/mtwebmapper/web/css/font-awesome.css
vendored
Normal file
BIN
cmd/mtwebmapper/web/css/images/markers-matte.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
cmd/mtwebmapper/web/css/images/markers-matte@2x.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
cmd/mtwebmapper/web/css/images/markers-plain.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
cmd/mtwebmapper/web/css/images/markers-shadow.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
cmd/mtwebmapper/web/css/images/markers-shadow@2x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
cmd/mtwebmapper/web/css/images/markers-soft.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
cmd/mtwebmapper/web/css/images/markers-soft@2x.png
Normal file
After Width: | Height: | Size: 65 KiB |
124
cmd/mtwebmapper/web/css/leaflet.awesome-markers.css
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
Author: L. Voogdt
|
||||
License: MIT
|
||||
Version: 1.0
|
||||
*/
|
||||
|
||||
/* Marker setup */
|
||||
.awesome-marker {
|
||||
background: url('images/markers-soft.png') no-repeat 0 0;
|
||||
width: 35px;
|
||||
height: 46px;
|
||||
position:absolute;
|
||||
left:0;
|
||||
top:0;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.awesome-marker-shadow {
|
||||
background: url('images/markers-shadow.png') no-repeat 0 0;
|
||||
width: 36px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Retina displays */
|
||||
@media (min--moz-device-pixel-ratio: 1.5),(-o-min-device-pixel-ratio: 3/2),
|
||||
(-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5),(min-resolution: 1.5dppx) {
|
||||
.awesome-marker {
|
||||
background-image: url('images/markers-soft@2x.png');
|
||||
background-size: 720px 46px;
|
||||
}
|
||||
.awesome-marker-shadow {
|
||||
background-image: url('images/markers-shadow@2x.png');
|
||||
background-size: 35px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.awesome-marker i {
|
||||
color: #333;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.awesome-marker .icon-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
.awesome-marker-icon-red {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-darkred {
|
||||
background-position: -180px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-lightred {
|
||||
background-position: -360px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-orange {
|
||||
background-position: -36px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-beige {
|
||||
background-position: -396px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-green {
|
||||
background-position: -72px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-darkgreen {
|
||||
background-position: -252px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-lightgreen {
|
||||
background-position: -432px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-blue {
|
||||
background-position: -108px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-darkblue {
|
||||
background-position: -216px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-lightblue {
|
||||
background-position: -468px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-purple {
|
||||
background-position: -144px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-darkpurple {
|
||||
background-position: -288px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-pink {
|
||||
background-position: -504px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-cadetblue {
|
||||
background-position: -324px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-white {
|
||||
background-position: -574px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-gray {
|
||||
background-position: -648px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-lightgray {
|
||||
background-position: -612px 0;
|
||||
}
|
||||
|
||||
.awesome-marker-icon-black {
|
||||
background-position: -682px 0;
|
||||
}
|
478
cmd/mtwebmapper/web/css/leaflet.css
Normal file
@ -0,0 +1,478 @@
|
||||
/* required styles */
|
||||
|
||||
.leaflet-map-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-pane,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-overlay-pane,
|
||||
.leaflet-shadow-pane,
|
||||
.leaflet-marker-pane,
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-overlay-pane svg,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
-ms-touch-action: none;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container img {
|
||||
max-width: none !important;
|
||||
}
|
||||
/* stupid Android 2 doesn't understand "max-width: none" properly */
|
||||
.leaflet-container img.leaflet-image-layer {
|
||||
max-width: 15000px !important;
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-tile-pane { z-index: 2; }
|
||||
.leaflet-objects-pane { z-index: 3; }
|
||||
.leaflet-overlay-pane { z-index: 4; }
|
||||
.leaflet-shadow-pane { z-index: 5; }
|
||||
.leaflet-marker-pane { z-index: 6; }
|
||||
.leaflet-popup-pane { z-index: 7; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-tile,
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
-o-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-tile-loaded,
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile,
|
||||
.leaflet-touching .leaflet-zoom-animated {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
-o-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-container {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-container,
|
||||
.leaflet-dragging .leaflet-clickable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
}
|
||||
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline: 0;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-container a.leaflet-active {
|
||||
outline: 2px solid orange;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
.leaflet-control-zoom-out {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in {
|
||||
font-size: 22px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-container .leaflet-control-attribution,
|
||||
.leaflet-container .leaflet-control-scale {
|
||||
font-size: 11px;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 19px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 18px 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
margin: 0 auto;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 4px 4px 0 0;
|
||||
text-align: center;
|
||||
width: 18px;
|
||||
height: 14px;
|
||||
font: 16px/14px Tahoma, Verdana, sans-serif;
|
||||
color: #c3c3c3;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover {
|
||||
color: #999;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip-container {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
BIN
cmd/mtwebmapper/web/fonts/fontawesome-webfont.ttf
Normal file
BIN
cmd/mtwebmapper/web/fonts/fontawesome-webfont.woff
Normal file
152
cmd/mtwebmapper/web/index.html
Normal file
@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Minetest demo map</title>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="css/leaflet.css" />
|
||||
<link rel="stylesheet" href="css/Leaflet.Coordinates-0.1.4.css" />
|
||||
<link rel="stylesheet" href="css/font-awesome.css" />
|
||||
<link rel="stylesheet" href="css/leaflet.awesome-markers.css" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#map {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #111111;
|
||||
}
|
||||
|
||||
.leaflet-container {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.leaflet-control-coordinates,
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
|
||||
background-color:rgba(255,255,255,.85);
|
||||
}
|
||||
.awesome-marker i {
|
||||
font-size: 18px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<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 type="text/javascript" src="js/leaflet.ajax.js"></script>
|
||||
<script type="text/javascript" src="js/leaflet.awesome-markers.js"></script>
|
||||
<script>
|
||||
|
||||
var useWebsocket = true; // Set to true if you want websocket support
|
||||
|
||||
L.Projection.NoWrap = {
|
||||
project: function (latlng) {
|
||||
return new L.Point(latlng.lat, latlng.lng);
|
||||
},
|
||||
|
||||
unproject: function (point, unbounded) {
|
||||
return new L.LatLng(point.x, point.y, true);
|
||||
}
|
||||
};
|
||||
|
||||
L.CRS.Direct = L.Util.extend({}, L.CRS, {
|
||||
code: 'Direct',
|
||||
projection: L.Projection.NoWrap,
|
||||
transformation: new L.Transformation(1.0/65536, 30928.0/65536, -1.0/65536, 34608.0/65536)
|
||||
});
|
||||
|
||||
var world = new L.tileLayer('map/{z}/{x}/{y}.png', {
|
||||
minZoom: 0,
|
||||
maxZoom: 16,
|
||||
attribution: 'Demo world',
|
||||
continuousWorld: false,
|
||||
noWrap: true,
|
||||
tms: true,
|
||||
unloadInvisibleTiles: true
|
||||
});
|
||||
|
||||
var players = L.geoJson.ajax('/players', {
|
||||
pointToLayer: function(feature, latlng) {
|
||||
return L.marker(latlng, {
|
||||
icon: L.AwesomeMarkers.icon({
|
||||
icon: 'male',
|
||||
iconColor: 'black',
|
||||
prefix: 'fa',
|
||||
markerColor: 'orange'
|
||||
}),
|
||||
title: feature.properties.name
|
||||
})
|
||||
}
|
||||
});
|
||||
var rasterMaps = {
|
||||
"A demo world": world,
|
||||
};
|
||||
|
||||
var latest = world
|
||||
|
||||
var overlayMaps = {'Players': players};
|
||||
|
||||
var map = L.map('map', {
|
||||
center: [0,0],
|
||||
zoom: 3,
|
||||
layers: [latest],
|
||||
worldCopyJump: false,
|
||||
crs: L.CRS.Direct});
|
||||
|
||||
L.control.coordinates({
|
||||
position:"topright", //optional default "bootomright"
|
||||
decimals:0, //optional default 4
|
||||
decimalSeperator:".", //optional default "."
|
||||
labelTemplateLat:"X: {y}", //optional default "Lat: {y}"
|
||||
labelTemplateLng:"Y: {x}", //optional default "Lng: {x}"
|
||||
enableUserInput:false, //optional default true
|
||||
useDMS:false, //optional default false
|
||||
useLatLngOrder: true //ordering of labels, default false-> lng-lat
|
||||
}).addTo(map);
|
||||
|
||||
var manualUpdateControl;
|
||||
if (useWebsocket && 'WebSocket' in window) {
|
||||
L.autoUpdate('autoUpdate', function(pressed) {
|
||||
var styleDec = manualUpdateControl.getContainer().style;
|
||||
styleDec.visibility = pressed ? 'hidden' : 'visible';
|
||||
},
|
||||
players);
|
||||
}
|
||||
var layersControl = new L.Control.Layers(rasterMaps, overlayMaps, {collapsed: false});
|
||||
map.addControl(layersControl);
|
||||
|
||||
manualUpdateControl = L.easyButton('fa-refresh',
|
||||
function (){
|
||||
var tiles = document.getElementsByTagName("img");
|
||||
for (var i = 0; i < tiles.length; i++) {
|
||||
var img = tiles[i];
|
||||
var cl = img.getAttribute("class");
|
||||
if (cl.indexOf("leaflet-tile-loaded") >= 0) {
|
||||
var src = img.src;
|
||||
var idx = src.lastIndexOf("#");
|
||||
if (idx >= 0) {
|
||||
src = src.substring(0, idx);
|
||||
}
|
||||
img.src = src + "#" + Math.random();
|
||||
}
|
||||
}
|
||||
//map._resetView(map.getCenter(), map.getZoom(), false);
|
||||
players.refresh("/players");
|
||||
},
|
||||
'Update view'
|
||||
);
|
||||
var hash = new L.Hash(map)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
2
cmd/mtwebmapper/web/js/Leaflet.Coordinates-0.1.4.min.js
vendored
Normal file
187
cmd/mtwebmapper/web/js/auto-update.js
Normal file
@ -0,0 +1,187 @@
|
||||
L.Control.AutoUpdate = L.Control.extend({
|
||||
options: {
|
||||
position: 'topleft',
|
||||
label: 'Automatic update',
|
||||
layer: undefined
|
||||
},
|
||||
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;
|
||||
},
|
||||
|
||||
switchButtons: function() {
|
||||
if (this.pressed) {
|
||||
this.pressed = false;
|
||||
this.iconStart.setAttribute('class', 'fa fa-pause');
|
||||
this.autoUpdate();
|
||||
} else {
|
||||
this.pressed = true;
|
||||
this.iconStart.setAttribute('class', 'fa fa-play');
|
||||
this.stopUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
cbClick: function (e) {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
this.intendedFunction(this.pressed);
|
||||
this.switchButtons();
|
||||
},
|
||||
|
||||
intendedFunction: function() {
|
||||
alert('no function selected');
|
||||
},
|
||||
|
||||
stopUpdate: function() {
|
||||
if (this.socket) {
|
||||
var s = this.socket;
|
||||
this.socket = null;
|
||||
s.close();
|
||||
}
|
||||
},
|
||||
|
||||
autoUpdate: function() {
|
||||
var me = this;
|
||||
this.socket = new WebSocket('ws://' + window.location.host + '/ws');
|
||||
|
||||
this.socket.onerror = function(evt) {
|
||||
me.stopUpdate();
|
||||
me.switchButtons();
|
||||
};
|
||||
|
||||
this.socket.onclose = function(evt) {
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
this.socket.onopen = function(evt) {
|
||||
// Sending pings every 5 secs to keep connection alive.
|
||||
var heartbeat = function() {
|
||||
if (heartbeat && me.socket) {
|
||||
me.socket.send("PING");
|
||||
setTimeout(heartbeat, 8000);
|
||||
} else {
|
||||
// Prevent sending pings to re-opened sockets.
|
||||
heartbeat = null;
|
||||
}
|
||||
};
|
||||
setTimeout(heartbeat, 8000);
|
||||
};
|
||||
|
||||
this.socket.onmessage = function(evt) {
|
||||
|
||||
var json = evt.data;
|
||||
if (!(typeof json === "string")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var msg;
|
||||
try {
|
||||
msg = JSON.parse(json);
|
||||
}
|
||||
catch (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.players) {
|
||||
me.options.layer.clearLayers();
|
||||
me.options.layer.addData(msg.players);
|
||||
}
|
||||
|
||||
var tilesData = msg.tiles;
|
||||
if (!tilesData) {
|
||||
return;
|
||||
}
|
||||
|
||||
var invalidate = function(td) {
|
||||
|
||||
var pyramid = new Array(9);
|
||||
var last = new Object();
|
||||
pyramid[8] = last;
|
||||
|
||||
for (var i = 0; i < td.length; i++) {
|
||||
var xz = td[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);
|
||||
};
|
||||
} (tilesData);
|
||||
|
||||
|
||||
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.indexOf('leaflet-tile-loaded') < 0) {
|
||||
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, layer, cbMap) {
|
||||
var control = new L.Control.AutoUpdate();
|
||||
if (cbLabel) {
|
||||
control.options.label = cbLabel;
|
||||
}
|
||||
|
||||
if (cbFunc) {
|
||||
control.intendedFunction = cbFunc;
|
||||
}
|
||||
|
||||
if (layer) {
|
||||
control.options.layer = layer;
|
||||
}
|
||||
|
||||
if (cbMap === '') {
|
||||
return control;
|
||||
}
|
||||
else if (cbMap) {
|
||||
cbMap.addControl(control);
|
||||
}
|
||||
else {
|
||||
map.addControl(control);
|
||||
}
|
||||
return control;
|
||||
};
|
48
cmd/mtwebmapper/web/js/easy-button.js
Normal file
@ -0,0 +1,48 @@
|
||||
L.Control.EasyButtons = L.Control.extend({
|
||||
options: {
|
||||
position: 'topleft',
|
||||
title: '',
|
||||
intentedIcon: 'fa-circle-o'
|
||||
},
|
||||
|
||||
onAdd: function () {
|
||||
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
|
||||
|
||||
this.link = L.DomUtil.create('a', 'leaflet-bar-part', container);
|
||||
L.DomUtil.create('i', 'fa fa-lg ' + this.options.intentedIcon , this.link);
|
||||
this.link.href = '#';
|
||||
|
||||
L.DomEvent.on(this.link, 'click', this._click, this);
|
||||
this.link.title = this.options.title;
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
intendedFunction: function(){ alert('no function selected');},
|
||||
|
||||
_click: function (e) {
|
||||
L.DomEvent.stopPropagation(e);
|
||||
L.DomEvent.preventDefault(e);
|
||||
this.intendedFunction();
|
||||
},
|
||||
});
|
||||
|
||||
L.easyButton = function( btnIcon , btnFunction , btnTitle , btnMap ) {
|
||||
var newControl = new L.Control.EasyButtons;
|
||||
if (btnIcon) newControl.options.intentedIcon = btnIcon;
|
||||
|
||||
if ( typeof btnFunction === 'function'){
|
||||
newControl.intendedFunction = btnFunction;
|
||||
}
|
||||
|
||||
if (btnTitle) newControl.options.title = btnTitle;
|
||||
|
||||
if ( btnMap == '' ){
|
||||
// skip auto addition
|
||||
} else if ( btnMap ) {
|
||||
btnMap.addControl(newControl);
|
||||
} else {
|
||||
map.addControl(newControl);
|
||||
}
|
||||
return newControl;
|
||||
};
|
162
cmd/mtwebmapper/web/js/leaflet-hash.js
Normal file
@ -0,0 +1,162 @@
|
||||
(function(window) {
|
||||
var HAS_HASHCHANGE = (function() {
|
||||
var doc_mode = window.documentMode;
|
||||
return ('onhashchange' in window) &&
|
||||
(doc_mode === undefined || doc_mode > 7);
|
||||
})();
|
||||
|
||||
L.Hash = function(map) {
|
||||
this.onHashChange = L.Util.bind(this.onHashChange, this);
|
||||
|
||||
if (map) {
|
||||
this.init(map);
|
||||
}
|
||||
};
|
||||
|
||||
L.Hash.parseHash = function(hash) {
|
||||
if(hash.indexOf('#') === 0) {
|
||||
hash = hash.substr(1);
|
||||
}
|
||||
var args = hash.split("/");
|
||||
if (args.length == 3) {
|
||||
var zoom = parseInt(args[0], 10),
|
||||
lat = parseFloat(args[1]),
|
||||
lon = parseFloat(args[2]);
|
||||
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
|
||||
return false;
|
||||
} else {
|
||||
return {
|
||||
center: new L.LatLng(lat, lon),
|
||||
zoom: zoom
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
L.Hash.formatHash = function(map) {
|
||||
var center = map.getCenter(),
|
||||
zoom = map.getZoom(),
|
||||
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
|
||||
|
||||
return "#" + [zoom,
|
||||
center.lat.toFixed(precision),
|
||||
center.lng.toFixed(precision)
|
||||
].join("/");
|
||||
},
|
||||
|
||||
L.Hash.prototype = {
|
||||
map: null,
|
||||
lastHash: null,
|
||||
|
||||
parseHash: L.Hash.parseHash,
|
||||
formatHash: L.Hash.formatHash,
|
||||
|
||||
init: function(map) {
|
||||
this.map = map;
|
||||
|
||||
// reset the hash
|
||||
this.lastHash = null;
|
||||
this.onHashChange();
|
||||
|
||||
if (!this.isListening) {
|
||||
this.startListening();
|
||||
}
|
||||
},
|
||||
|
||||
removeFrom: function(map) {
|
||||
if (this.changeTimeout) {
|
||||
clearTimeout(this.changeTimeout);
|
||||
}
|
||||
|
||||
if (this.isListening) {
|
||||
this.stopListening();
|
||||
}
|
||||
|
||||
this.map = null;
|
||||
},
|
||||
|
||||
onMapMove: function() {
|
||||
// bail if we're moving the map (updating from a hash),
|
||||
// or if the map is not yet loaded
|
||||
|
||||
if (this.movingMap || !this.map._loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var hash = this.formatHash(this.map);
|
||||
if (this.lastHash != hash) {
|
||||
location.replace(hash);
|
||||
this.lastHash = hash;
|
||||
}
|
||||
},
|
||||
|
||||
movingMap: false,
|
||||
update: function() {
|
||||
var hash = location.hash;
|
||||
if (hash === this.lastHash) {
|
||||
return;
|
||||
}
|
||||
var parsed = this.parseHash(hash);
|
||||
if (parsed) {
|
||||
this.movingMap = true;
|
||||
|
||||
this.map.setView(parsed.center, parsed.zoom);
|
||||
|
||||
this.movingMap = false;
|
||||
} else {
|
||||
this.onMapMove(this.map);
|
||||
}
|
||||
},
|
||||
|
||||
// defer hash change updates every 100ms
|
||||
changeDefer: 100,
|
||||
changeTimeout: null,
|
||||
onHashChange: function() {
|
||||
// throttle calls to update() so that they only happen every
|
||||
// `changeDefer` ms
|
||||
if (!this.changeTimeout) {
|
||||
var that = this;
|
||||
this.changeTimeout = setTimeout(function() {
|
||||
that.update();
|
||||
that.changeTimeout = null;
|
||||
}, this.changeDefer);
|
||||
}
|
||||
},
|
||||
|
||||
isListening: false,
|
||||
hashChangeInterval: null,
|
||||
startListening: function() {
|
||||
this.map.on("moveend", this.onMapMove, this);
|
||||
|
||||
if (HAS_HASHCHANGE) {
|
||||
L.DomEvent.addListener(window, "hashchange", this.onHashChange);
|
||||
} else {
|
||||
clearInterval(this.hashChangeInterval);
|
||||
this.hashChangeInterval = setInterval(this.onHashChange, 50);
|
||||
}
|
||||
this.isListening = true;
|
||||
},
|
||||
|
||||
stopListening: function() {
|
||||
this.map.off("moveend", this.onMapMove, this);
|
||||
|
||||
if (HAS_HASHCHANGE) {
|
||||
L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
|
||||
} else {
|
||||
clearInterval(this.hashChangeInterval);
|
||||
}
|
||||
this.isListening = false;
|
||||
}
|
||||
};
|
||||
L.hash = function(map) {
|
||||
return new L.Hash(map);
|
||||
};
|
||||
L.Map.prototype.addHash = function() {
|
||||
this._hash = L.hash(this);
|
||||
};
|
||||
L.Map.prototype.removeHash = function() {
|
||||
this._hash.removeFrom();
|
||||
};
|
||||
})(window);
|
740
cmd/mtwebmapper/web/js/leaflet.ajax.js
Normal file
@ -0,0 +1,740 @@
|
||||
;(function(){
|
||||
|
||||
/**
|
||||
* Require the given path.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Object} exports
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function require(path, parent, orig) {
|
||||
var resolved = require.resolve(path);
|
||||
|
||||
// lookup failed
|
||||
if (null == resolved) {
|
||||
orig = orig || path;
|
||||
parent = parent || 'root';
|
||||
var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
|
||||
err.path = orig;
|
||||
err.parent = parent;
|
||||
err.require = true;
|
||||
throw err;
|
||||
}
|
||||
|
||||
var module = require.modules[resolved];
|
||||
|
||||
// perform real require()
|
||||
// by invoking the module's
|
||||
// registered function
|
||||
if (!module.exports) {
|
||||
module.exports = {};
|
||||
module.client = module.component = true;
|
||||
module.call(this, module.exports, require.relative(resolved), module);
|
||||
}
|
||||
|
||||
return module.exports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered modules.
|
||||
*/
|
||||
|
||||
require.modules = {};
|
||||
|
||||
/**
|
||||
* Registered aliases.
|
||||
*/
|
||||
|
||||
require.aliases = {};
|
||||
|
||||
/**
|
||||
* Resolve `path`.
|
||||
*
|
||||
* Lookup:
|
||||
*
|
||||
* - PATH/index.js
|
||||
* - PATH.js
|
||||
* - PATH
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {String} path or null
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.resolve = function(path) {
|
||||
if (path.charAt(0) === '/') path = path.slice(1);
|
||||
|
||||
var paths = [
|
||||
path,
|
||||
path + '.js',
|
||||
path + '.json',
|
||||
path + '/index.js',
|
||||
path + '/index.json'
|
||||
];
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = paths[i];
|
||||
if (require.modules.hasOwnProperty(path)) return path;
|
||||
if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize `path` relative to the current path.
|
||||
*
|
||||
* @param {String} curr
|
||||
* @param {String} path
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.normalize = function(curr, path) {
|
||||
var segs = [];
|
||||
|
||||
if ('.' != path.charAt(0)) return path;
|
||||
|
||||
curr = curr.split('/');
|
||||
path = path.split('/');
|
||||
|
||||
for (var i = 0; i < path.length; ++i) {
|
||||
if ('..' == path[i]) {
|
||||
curr.pop();
|
||||
} else if ('.' != path[i] && '' != path[i]) {
|
||||
segs.push(path[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return curr.concat(segs).join('/');
|
||||
};
|
||||
|
||||
/**
|
||||
* Register module at `path` with callback `definition`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} definition
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.register = function(path, definition) {
|
||||
require.modules[path] = definition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Alias a module definition.
|
||||
*
|
||||
* @param {String} from
|
||||
* @param {String} to
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.alias = function(from, to) {
|
||||
if (!require.modules.hasOwnProperty(from)) {
|
||||
throw new Error('Failed to alias "' + from + '", it does not exist');
|
||||
}
|
||||
require.aliases[to] = from;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a require function relative to the `parent` path.
|
||||
*
|
||||
* @param {String} parent
|
||||
* @return {Function}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
require.relative = function(parent) {
|
||||
var p = require.normalize(parent, '..');
|
||||
|
||||
/**
|
||||
* lastIndexOf helper.
|
||||
*/
|
||||
|
||||
function lastIndexOf(arr, obj) {
|
||||
var i = arr.length;
|
||||
while (i--) {
|
||||
if (arr[i] === obj) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The relative require() itself.
|
||||
*/
|
||||
|
||||
function localRequire(path) {
|
||||
var resolved = localRequire.resolve(path);
|
||||
return require(resolved, parent, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve relative to the parent.
|
||||
*/
|
||||
|
||||
localRequire.resolve = function(path) {
|
||||
var c = path.charAt(0);
|
||||
if ('/' == c) return path.slice(1);
|
||||
if ('.' == c) return require.normalize(p, path);
|
||||
|
||||
// resolve deps by returning
|
||||
// the dep in the nearest "deps"
|
||||
// directory
|
||||
var segs = parent.split('/');
|
||||
var i = lastIndexOf(segs, 'deps') + 1;
|
||||
if (!i) i = 0;
|
||||
path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
|
||||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if module is defined at `path`.
|
||||
*/
|
||||
|
||||
localRequire.exists = function(path) {
|
||||
return require.modules.hasOwnProperty(localRequire.resolve(path));
|
||||
};
|
||||
|
||||
return localRequire;
|
||||
};
|
||||
require.register("calvinmetcalf-setImmediate/lib/index.js", function(exports, require, module){
|
||||
"use strict";
|
||||
var types = [
|
||||
require("./nextTick"),
|
||||
require("./mutation"),
|
||||
require("./postMessage"),
|
||||
require("./messageChannel"),
|
||||
require("./stateChange"),
|
||||
require("./timeout")
|
||||
];
|
||||
var handlerQueue = [];
|
||||
|
||||
function drainQueue() {
|
||||
var i = 0,
|
||||
task,
|
||||
innerQueue = handlerQueue;
|
||||
handlerQueue = [];
|
||||
/*jslint boss: true */
|
||||
while (task = innerQueue[i++]) {
|
||||
task();
|
||||
}
|
||||
}
|
||||
var nextTick;
|
||||
types.some(function (obj) {
|
||||
var t = obj.test();
|
||||
if (t) {
|
||||
nextTick = obj.install(drainQueue);
|
||||
}
|
||||
return t;
|
||||
});
|
||||
var retFunc = function (task) {
|
||||
var len, args;
|
||||
if (arguments.length > 1 && typeof task === "function") {
|
||||
args = Array.prototype.slice.call(arguments, 1);
|
||||
args.unshift(undefined);
|
||||
task = task.bind.apply(task, args);
|
||||
}
|
||||
if ((len = handlerQueue.push(task)) === 1) {
|
||||
nextTick(drainQueue);
|
||||
}
|
||||
return len;
|
||||
};
|
||||
retFunc.clear = function (n) {
|
||||
if (n <= handlerQueue.length) {
|
||||
handlerQueue[n - 1] = function () {};
|
||||
}
|
||||
return this;
|
||||
};
|
||||
module.exports = retFunc;
|
||||
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/nextTick.js", function(exports, require, module){
|
||||
"use strict";
|
||||
exports.test = function () {
|
||||
// Don't get fooled by e.g. browserify environments.
|
||||
return typeof process === "object" && Object.prototype.toString.call(process) === "[object process]";
|
||||
};
|
||||
|
||||
exports.install = function () {
|
||||
return process.nextTick;
|
||||
};
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/postMessage.js", function(exports, require, module){
|
||||
"use strict";
|
||||
var globe = require("./global");
|
||||
exports.test = function () {
|
||||
// The test against `importScripts` prevents this implementation from being installed inside a web worker,
|
||||
// where `global.postMessage` means something completely different and can"t be used for this purpose.
|
||||
|
||||
if (!globe.postMessage || globe.importScripts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var postMessageIsAsynchronous = true;
|
||||
var oldOnMessage = globe.onmessage;
|
||||
globe.onmessage = function () {
|
||||
postMessageIsAsynchronous = false;
|
||||
};
|
||||
globe.postMessage("", "*");
|
||||
globe.onmessage = oldOnMessage;
|
||||
|
||||
return postMessageIsAsynchronous;
|
||||
};
|
||||
|
||||
exports.install = function (func) {
|
||||
var codeWord = "com.calvinmetcalf.setImmediate" + Math.random();
|
||||
function globalMessage(event) {
|
||||
if (event.source === globe && event.data === codeWord) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
if (globe.addEventListener) {
|
||||
globe.addEventListener("message", globalMessage, false);
|
||||
} else {
|
||||
globe.attachEvent("onmessage", globalMessage);
|
||||
}
|
||||
return function () {
|
||||
globe.postMessage(codeWord, "*");
|
||||
};
|
||||
};
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/messageChannel.js", function(exports, require, module){
|
||||
"use strict";
|
||||
var globe = require("./global");
|
||||
exports.test = function () {
|
||||
return !!globe.MessageChannel;
|
||||
};
|
||||
|
||||
exports.install = function (func) {
|
||||
var channel = new globe.MessageChannel();
|
||||
channel.port1.onmessage = func;
|
||||
return function () {
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
};
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/stateChange.js", function(exports, require, module){
|
||||
"use strict";
|
||||
var globe = require("./global");
|
||||
exports.test = function () {
|
||||
return "document" in globe && "onreadystatechange" in globe.document.createElement("script");
|
||||
};
|
||||
|
||||
exports.install = function (handle) {
|
||||
return function () {
|
||||
|
||||
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
|
||||
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
|
||||
var scriptEl = globe.document.createElement("script");
|
||||
scriptEl.onreadystatechange = function () {
|
||||
handle();
|
||||
|
||||
scriptEl.onreadystatechange = null;
|
||||
scriptEl.parentNode.removeChild(scriptEl);
|
||||
scriptEl = null;
|
||||
};
|
||||
globe.document.documentElement.appendChild(scriptEl);
|
||||
|
||||
return handle;
|
||||
};
|
||||
};
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/timeout.js", function(exports, require, module){
|
||||
"use strict";
|
||||
exports.test = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.install = function (t) {
|
||||
return function () {
|
||||
setTimeout(t, 0);
|
||||
};
|
||||
};
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/global.js", function(exports, require, module){
|
||||
module.exports = typeof global === "object" && global ? global : this;
|
||||
});
|
||||
require.register("calvinmetcalf-setImmediate/lib/mutation.js", function(exports, require, module){
|
||||
"use strict";
|
||||
//based off rsvp
|
||||
//https://github.com/tildeio/rsvp.js/blob/master/lib/rsvp/async.js
|
||||
var globe = require("./global");
|
||||
|
||||
var MutationObserver = globe.MutationObserver || globe.WebKitMutationObserver;
|
||||
|
||||
exports.test = function () {
|
||||
return MutationObserver;
|
||||
};
|
||||
|
||||
exports.install = function (handle) {
|
||||
var observer = new MutationObserver(handle);
|
||||
var element = globe.document.createElement("div");
|
||||
observer.observe(element, { attributes: true });
|
||||
|
||||
// Chrome Memory Leak: https://bugs.webkit.org/show_bug.cgi?id=93661
|
||||
globe.addEventListener("unload", function () {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}, false);
|
||||
return function () {
|
||||
element.setAttribute("drainQueue", "drainQueue");
|
||||
};
|
||||
};
|
||||
});
|
||||
require.register("lie/lie.js", function(exports, require, module){
|
||||
var immediate = require('immediate');
|
||||
// Creates a deferred: an object with a promise and corresponding resolve/reject methods
|
||||
function Promise(resolver) {
|
||||
if (!(this instanceof Promise)) {
|
||||
return new Promise(resolver);
|
||||
}
|
||||
var queue = [];
|
||||
var resolved = false;
|
||||
// The `handler` variable points to the function that will
|
||||
// 1) handle a .then(onFulfilled, onRejected) call
|
||||
// 2) handle a .resolve or .reject call (if not fulfilled)
|
||||
// Before 2), `handler` holds a queue of callbacks.
|
||||
// After 2), `handler` is a simple .then handler.
|
||||
// We use only one function to save memory and complexity.
|
||||
// Case 1) handle a .then(onFulfilled, onRejected) call
|
||||
function pending(onFulfilled, onRejected){
|
||||
return Promise(function(resolver,rejecter){
|
||||
queue.push({
|
||||
resolve: onFulfilled,
|
||||
reject: onRejected,
|
||||
resolver:resolver,
|
||||
rejecter:rejecter
|
||||
});
|
||||
});
|
||||
}
|
||||
function then(onFulfilled, onRejected) {
|
||||
return resolved?resolved(onFulfilled, onRejected):pending(onFulfilled, onRejected);
|
||||
}
|
||||
// Case 2) handle a .resolve or .reject call
|
||||
// (`onFulfilled` acts as a sentinel)
|
||||
// The actual function signature is
|
||||
// .re[ject|solve](sentinel, success, value)
|
||||
function resolve(success, value){
|
||||
var action = success ? 'resolve' : 'reject';
|
||||
var queued;
|
||||
var callback;
|
||||
for (var i = 0, l = queue.length; i < l; i++) {
|
||||
queued = queue[i];
|
||||
callback = queued[action];
|
||||
if (typeof callback === 'function') {
|
||||
immediate(execute,callback, value, queued.resolver, queued.rejecter);
|
||||
}else if(success){
|
||||
queued.resolver(value);
|
||||
}else{
|
||||
queued.rejecter(value);
|
||||
}
|
||||
}
|
||||
// Replace this handler with a simple resolved or rejected handler
|
||||
resolved = createHandler(then, value, success);
|
||||
}
|
||||
this.then = then;
|
||||
function yes(value) {
|
||||
if (!resolved) {
|
||||
resolve(true, value);
|
||||
}
|
||||
}
|
||||
function no (reason) {
|
||||
if (!resolved) {
|
||||
resolve(false, reason);
|
||||
}
|
||||
}
|
||||
try{
|
||||
resolver(function(a){
|
||||
if(a && typeof a.then==='function'){
|
||||
a.then(yes,no);
|
||||
}else{
|
||||
yes(a);
|
||||
}
|
||||
},no);
|
||||
}catch(e){
|
||||
no(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a fulfilled or rejected .then function
|
||||
function createHandler(then, value, success) {
|
||||
return function(onFulfilled, onRejected) {
|
||||
var callback = success ? onFulfilled : onRejected;
|
||||
if (typeof callback !== 'function') {
|
||||
return Promise(function(resolve,reject){
|
||||
then(resolve,reject);
|
||||
});
|
||||
}
|
||||
return Promise(function(resolve,reject){
|
||||
immediate(execute,callback,value,resolve,reject);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Executes the callback with the specified value,
|
||||
// resolving or rejecting the deferred
|
||||
function execute(callback, value, resolve, reject) {
|
||||
try {
|
||||
var result = callback(value);
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(resolve, reject);
|
||||
}
|
||||
else {
|
||||
resolve(result);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
module.exports = Promise;
|
||||
|
||||
});
|
||||
require.alias("calvinmetcalf-setImmediate/lib/index.js", "lie/deps/immediate/lib/index.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/nextTick.js", "lie/deps/immediate/lib/nextTick.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/postMessage.js", "lie/deps/immediate/lib/postMessage.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/messageChannel.js", "lie/deps/immediate/lib/messageChannel.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/stateChange.js", "lie/deps/immediate/lib/stateChange.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/timeout.js", "lie/deps/immediate/lib/timeout.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/global.js", "lie/deps/immediate/lib/global.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/mutation.js", "lie/deps/immediate/lib/mutation.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/index.js", "lie/deps/immediate/index.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/index.js", "immediate/index.js");
|
||||
require.alias("calvinmetcalf-setImmediate/lib/index.js", "calvinmetcalf-setImmediate/index.js");
|
||||
|
||||
require.alias("lie/lie.js", "lie/index.js");
|
||||
|
||||
L.Util.Promise = require("lie");
|
||||
})();
|
||||
|
||||
L.Util.ajax = function(url, options) {
|
||||
'use strict';
|
||||
options = options || {};
|
||||
if (options.jsonp) {
|
||||
return L.Util.ajax.jsonp(url, options);
|
||||
}
|
||||
var request;
|
||||
var cancel;
|
||||
var out = L.Util.Promise(function(resolve,reject){
|
||||
var Ajax;
|
||||
cancel=reject;
|
||||
// the following is from JavaScript: The Definitive Guide
|
||||
if (window.XMLHttpRequest === undefined) {
|
||||
Ajax = function() {
|
||||
try {
|
||||
return new ActiveXObject('Microsoft.XMLHTTP.6.0');
|
||||
}
|
||||
catch (e1) {
|
||||
try {
|
||||
return new ActiveXObject('Microsoft.XMLHTTP.3.0');
|
||||
}
|
||||
catch (e2) {
|
||||
reject('XMLHttpRequest is not supported');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
Ajax = window.XMLHttpRequest;
|
||||
}
|
||||
var response;
|
||||
request = new Ajax();
|
||||
request.open('GET', url);
|
||||
request.onreadystatechange = function() {
|
||||
/*jslint evil: true */
|
||||
if (request.readyState === 4) {
|
||||
if((request.status < 400&&options.local)|| request.status===200) {
|
||||
if (window.JSON) {
|
||||
response = JSON.parse(request.responseText);
|
||||
} else if (options.evil) {
|
||||
response = eval('(' + request.responseText + ')');
|
||||
}
|
||||
resolve(response);
|
||||
} else {
|
||||
if(!request.status){
|
||||
reject('Attempted cross origin request without CORS enabled');
|
||||
}else{
|
||||
reject(request.statusText);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
request.send();
|
||||
});
|
||||
out.then(null,function(reason){
|
||||
request.abort();
|
||||
return reason;
|
||||
});
|
||||
out.abort = cancel;
|
||||
return out;
|
||||
};
|
||||
|
||||
L.Util.jsonp = function(url, options) {
|
||||
options = options || {};
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var scriptNode = L.DomUtil.create('script', '', head);
|
||||
var cbName, ourl, cbSuffix, cancel;
|
||||
var out = L.Util.Promise(function(resolve, reject){
|
||||
cancel=reject;
|
||||
var cbParam = options.cbParam || 'callback';
|
||||
if (options.callbackName) {
|
||||
cbName = options.callbackName;
|
||||
}
|
||||
else {
|
||||
cbSuffix = '_' + ('' + Math.random()).slice(2);
|
||||
cbName = 'L.Util.jsonp.cb.' + cbSuffix;
|
||||
}
|
||||
scriptNode.type = 'text/javascript';
|
||||
if (cbSuffix) {
|
||||
L.Util.jsonp.cb[cbSuffix] = function(data) {
|
||||
head.removeChild(scriptNode);
|
||||
delete L.Util.jsonp.cb[cbSuffix];
|
||||
resolve(data);
|
||||
};
|
||||
}
|
||||
if (url.indexOf('?') === -1) {
|
||||
ourl = url + '?' + cbParam + '=' + cbName;
|
||||
}
|
||||
else {
|
||||
ourl = url + '&' + cbParam + '=' + cbName;
|
||||
}
|
||||
scriptNode.src = ourl;
|
||||
}).then(null,function(reason){
|
||||
head.removeChild(scriptNode);
|
||||
delete L.Util.ajax.cb[cbSuffix];
|
||||
return reason;
|
||||
});
|
||||
out.abort = cancel;
|
||||
return out;
|
||||
};
|
||||
L.Util.jsonp.cb = {};
|
||||
|
||||
L.GeoJSON.AJAX = L.GeoJSON.extend({
|
||||
defaultAJAXparams: {
|
||||
dataType: 'json',
|
||||
callbackParam: 'callback',
|
||||
local:false,
|
||||
middleware: function(f) {
|
||||
return f;
|
||||
}
|
||||
},
|
||||
initialize: function(url, options) {
|
||||
|
||||
this.urls = [];
|
||||
if (url) {
|
||||
if (typeof url === 'string') {
|
||||
this.urls.push(url);
|
||||
}
|
||||
else if (typeof url.pop === 'function') {
|
||||
this.urls = this.urls.concat(url);
|
||||
}
|
||||
else {
|
||||
options = url;
|
||||
url = undefined;
|
||||
}
|
||||
}
|
||||
var ajaxParams = L.Util.extend({}, this.defaultAJAXparams);
|
||||
|
||||
for (var i in options) {
|
||||
if (this.defaultAJAXparams.hasOwnProperty(i)) {
|
||||
ajaxParams[i] = options[i];
|
||||
}
|
||||
}
|
||||
this.ajaxParams = ajaxParams;
|
||||
this._layers = {};
|
||||
L.Util.setOptions(this, options);
|
||||
this.on('data:loaded', function() {
|
||||
if (this.filter) {
|
||||
this.refilter(this.filter);
|
||||
}
|
||||
}, this);
|
||||
var self = this;
|
||||
if (this.urls.length > 0) {
|
||||
L.Util.Promise(function(yes){
|
||||
yes();
|
||||
}).then(function(){
|
||||
self.addUrl();
|
||||
});
|
||||
}
|
||||
},
|
||||
clearLayers: function() {
|
||||
this.urls = [];
|
||||
L.GeoJSON.prototype.clearLayers.call(this);
|
||||
return this;
|
||||
},
|
||||
addUrl: function(url) {
|
||||
var self = this;
|
||||
if (url) {
|
||||
if (typeof url === 'string') {
|
||||
self.urls.push(url);
|
||||
}
|
||||
else if (typeof url.pop === 'function') {
|
||||
self.urls = self.urls.concat(url);
|
||||
}
|
||||
}
|
||||
var loading = self.urls.length;
|
||||
var done = 0;
|
||||
self.fire('data:loading');
|
||||
self.urls.forEach(function(url) {
|
||||
if (self.ajaxParams.dataType.toLowerCase() === 'json') {
|
||||
L.Util.ajax(url,self.ajaxParams).then(function(d) {
|
||||
var data = self.ajaxParams.middleware(d);
|
||||
self.addData(data);
|
||||
self.fire('data:progress',data);
|
||||
},function(err){
|
||||
self.fire('data:progress',{error:err});
|
||||
});
|
||||
}
|
||||
else if (self.ajaxParams.dataType.toLowerCase() === 'jsonp') {
|
||||
L.Util.jsonp(url,self.ajaxParams).then(function(d) {
|
||||
var data = self.ajaxParams.middleware(d);
|
||||
self.addData(data);
|
||||
self.fire('data:progress',data);
|
||||
},function(err){
|
||||
self.fire('data:progress',{error:err});
|
||||
});
|
||||
}
|
||||
});
|
||||
self.on('data:progress', function() {
|
||||
if (++done === loading) {
|
||||
self.fire('data:loaded');
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function(url) {
|
||||
url = url || this.urls;
|
||||
this.clearLayers();
|
||||
this.addUrl(url);
|
||||
},
|
||||
refilter: function(func) {
|
||||
if (typeof func !== 'function') {
|
||||
this.filter = false;
|
||||
this.eachLayer(function(a) {
|
||||
a.setStyle({
|
||||
stroke: true,
|
||||
clickable: true
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.filter = func;
|
||||
this.eachLayer(function(a) {
|
||||
|
||||
if (func(a.feature)) {
|
||||
a.setStyle({
|
||||
stroke: true,
|
||||
clickable: true
|
||||
});
|
||||
}
|
||||
else {
|
||||
a.setStyle({
|
||||
stroke: false,
|
||||
clickable: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
L.geoJson.ajax = function(geojson, options) {
|
||||
return new L.GeoJSON.AJAX(geojson, options);
|
||||
};
|
125
cmd/mtwebmapper/web/js/leaflet.awesome-markers.js
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
Leaflet.AwesomeMarkers, a plugin that adds colorful iconic markers for Leaflet, based on the Font Awesome icons
|
||||
(c) 2012-2013, Lennard Voogdt
|
||||
|
||||
http://leafletjs.com
|
||||
https://github.com/lvoogdt
|
||||
*/
|
||||
|
||||
/*global L*/
|
||||
|
||||
(function (window, document, undefined) {
|
||||
"use strict";
|
||||
/*
|
||||
* Leaflet.AwesomeMarkers assumes that you have already included the Leaflet library.
|
||||
*/
|
||||
|
||||
L.AwesomeMarkers = {};
|
||||
|
||||
L.AwesomeMarkers.version = '2.0.1';
|
||||
|
||||
L.AwesomeMarkers.Icon = L.Icon.extend({
|
||||
options: {
|
||||
iconSize: [35, 45],
|
||||
iconAnchor: [17, 42],
|
||||
popupAnchor: [1, -32],
|
||||
shadowAnchor: [10, 12],
|
||||
shadowSize: [36, 16],
|
||||
className: 'awesome-marker',
|
||||
prefix: 'glyphicon',
|
||||
spinClass: 'fa-spin',
|
||||
extraClasses: '',
|
||||
icon: 'home',
|
||||
markerColor: 'blue',
|
||||
iconColor: 'white'
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
options = L.Util.setOptions(this, options);
|
||||
},
|
||||
|
||||
createIcon: function () {
|
||||
var div = document.createElement('div'),
|
||||
options = this.options;
|
||||
|
||||
if (options.icon) {
|
||||
div.innerHTML = this._createInner();
|
||||
}
|
||||
|
||||
if (options.bgPos) {
|
||||
div.style.backgroundPosition =
|
||||
(-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
|
||||
}
|
||||
|
||||
this._setIconStyles(div, 'icon-' + options.markerColor);
|
||||
return div;
|
||||
},
|
||||
|
||||
_createInner: function() {
|
||||
var iconClass, iconSpinClass = "", iconColorClass = "", iconColorStyle = "", options = this.options;
|
||||
|
||||
if(options.icon.slice(0,options.prefix.length+1) === options.prefix + "-") {
|
||||
iconClass = options.icon;
|
||||
} else {
|
||||
iconClass = options.prefix + "-" + options.icon;
|
||||
}
|
||||
|
||||
if(options.spin && typeof options.spinClass === "string") {
|
||||
iconSpinClass = options.spinClass;
|
||||
}
|
||||
|
||||
if(options.iconColor) {
|
||||
if(options.iconColor === 'white' || options.iconColor === 'black') {
|
||||
iconColorClass = "icon-" + options.iconColor;
|
||||
} else {
|
||||
iconColorStyle = "style='color: " + options.iconColor + "' ";
|
||||
}
|
||||
}
|
||||
|
||||
return "<i " + iconColorStyle + "class='" + options.extraClasses + " " + options.prefix + " " + iconClass + " " + iconSpinClass + " " + iconColorClass + "'></i>";
|
||||
},
|
||||
|
||||
_setIconStyles: function (img, name) {
|
||||
var options = this.options,
|
||||
size = L.point(options[name === 'shadow' ? 'shadowSize' : 'iconSize']),
|
||||
anchor;
|
||||
|
||||
if (name === 'shadow') {
|
||||
anchor = L.point(options.shadowAnchor || options.iconAnchor);
|
||||
} else {
|
||||
anchor = L.point(options.iconAnchor);
|
||||
}
|
||||
|
||||
if (!anchor && size) {
|
||||
anchor = size.divideBy(2, true);
|
||||
}
|
||||
|
||||
img.className = 'awesome-marker-' + name + ' ' + options.className;
|
||||
|
||||
if (anchor) {
|
||||
img.style.marginLeft = (-anchor.x) + 'px';
|
||||
img.style.marginTop = (-anchor.y) + 'px';
|
||||
}
|
||||
|
||||
if (size) {
|
||||
img.style.width = size.x + 'px';
|
||||
img.style.height = size.y + 'px';
|
||||
}
|
||||
},
|
||||
|
||||
createShadow: function () {
|
||||
var div = document.createElement('div');
|
||||
|
||||
this._setIconStyles(div, 'shadow');
|
||||
return div;
|
||||
}
|
||||
});
|
||||
|
||||
L.AwesomeMarkers.icon = function (options) {
|
||||
return new L.AwesomeMarkers.Icon(options);
|
||||
};
|
||||
|
||||
}(this, document));
|
||||
|
||||
|
||||
|
9
cmd/mtwebmapper/web/js/leaflet.js
Normal file
BIN
cmd/mtwebmapper/web/markers/player.png
Normal file
After Width: | Height: | Size: 157 B |