Skip to content

Commit c994c7d

Browse files
committed
Made into a very simple HTTP server.
1 parent b1b4112 commit c994c7d

File tree

2 files changed

+203
-70
lines changed

2 files changed

+203
-70
lines changed

main.go

+14-70
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,31 @@
11
package main
22

33
import (
4-
"bytes"
54
"flag"
65
"fmt"
7-
"io"
8-
"os"
9-
"github.com/zerebubuth/govecamole"
6+
"log"
7+
"net/http"
8+
"runtime"
109
)
1110

12-
var outputFile = flag.String("outputFile", "tile.pbf", "The file name to write the output to.")
11+
var numProcs = flag.Int("numProcs", runtime.GOMAXPROCS(0), "The number of Mapnik processes to run. More processes will allow more parallelism, but also consume more resources.")
12+
var styleFile = flag.String("styleFile", "map.xml", "The Mapnik style file to load and serve.")
13+
var port = flag.Int("port", 8080, "The port number to start the HTTP server listening on.")
1314

1415
func main() {
1516
flag.Parse()
1617

17-
err := govecamole.RegisterDefaultDatasources()
18+
h, err := NewVecMapsHandler(*styleFile, *numProcs)
1819
if err != nil {
19-
fmt.Printf("Ooops, can't register datasources: %s\n", err.Error())
20+
fmt.Printf("Ooops, start vector maps handler: %s\n", err.Error())
2021
return
2122
}
23+
defer h.Close()
2224

23-
m, err := govecamole.New(256, 256)
24-
if err != nil {
25-
fmt.Printf("Ooops, got an error: %s\n", err.Error())
26-
return
27-
}
28-
defer m.Close()
29-
30-
sampleconf := `<!DOCTYPE Map>
31-
<Map srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
32-
<Style name="point">
33-
</Style>
34-
<Layer name="point" srs="+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs">
35-
<StyleName>point</StyleName>
36-
<Datasource>
37-
<Parameter name="type">csv</Parameter>
38-
<Parameter name="inline">
39-
type,WKT
40-
point,"POINT (0 0)"
41-
</Parameter>
42-
</Datasource>
43-
</Layer>
44-
</Map>`
45-
46-
err = m.LoadString(sampleconf, true, "sampleconf")
47-
if err != nil {
48-
fmt.Printf("Unable to load sample config string into map: %s\n", err.Error())
49-
return
50-
}
51-
52-
req, err := govecamole.NewRequestZXY(256, 256, 0, 0, 0)
53-
if err != nil {
54-
fmt.Printf("Unable to create a request: %s\n", err.Error())
55-
return
56-
}
57-
defer req.Close()
58-
59-
opts, err := govecamole.DefaultOptions()
60-
if err != nil {
61-
fmt.Printf("Unable to create default options: %s\n", err.Error())
62-
return
63-
}
64-
defer opts.Close()
65-
66-
var buf bytes.Buffer
67-
err = govecamole.Render(&buf, m, req, opts)
68-
if err != nil {
69-
fmt.Printf("Unable render tile: %s\n", err.Error())
70-
return
71-
}
72-
73-
fmt.Printf("Got tile size=%v\n", buf.Len())
74-
75-
file, err := os.Create(*outputFile)
76-
if err != nil {
77-
fmt.Printf("Unable to create output file %v: %s\n", *outputFile, err.Error())
78-
return
79-
}
80-
defer file.Close()
81-
82-
_, err = io.Copy(file, &buf)
83-
if err != nil {
84-
fmt.Printf("Unable to copy tile to file: %s\n", err.Error())
85-
return
25+
addr := fmt.Sprintf(":%d", *port)
26+
s := &http.Server{
27+
Addr: addr,
28+
Handler: h,
8629
}
30+
log.Fatal(s.ListenAndServe())
8731
}

server.go

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"github.com/zerebubuth/govecamole"
8+
"net/http"
9+
)
10+
11+
type response struct {
12+
tile []byte
13+
err error
14+
}
15+
16+
type request struct {
17+
z, x, y int
18+
reply chan<- response
19+
}
20+
21+
// renderTile renders a single tile
22+
func renderTile(z, x, y int, m *govecamole.VecMap) ([]byte, error) {
23+
req, err := govecamole.NewRequestZXY(256, 256, z, x, y)
24+
if err != nil {
25+
return nil, err
26+
}
27+
defer req.Close()
28+
29+
opts, err := govecamole.DefaultOptions()
30+
if err != nil {
31+
return nil, err
32+
}
33+
defer opts.Close()
34+
35+
var buf bytes.Buffer
36+
err = govecamole.Render(&buf, m, req, opts)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
return buf.Bytes(), nil
42+
}
43+
44+
// renderLoop listens on a channel for requests, renders them and writes the
45+
// result back on the channel in the request.
46+
func renderLoop(ch <-chan request, m *govecamole.VecMap) {
47+
// see note in startRenderer about this.
48+
defer m.Close()
49+
50+
for req := range ch {
51+
var r response
52+
r.tile, r.err = renderTile(req.z, req.x, req.y, m)
53+
req.reply<- r
54+
}
55+
}
56+
57+
// startRenderer starts a single renderer, returning an error if the setup
58+
// didn't work right.
59+
func startRenderer(ch <-chan request, configFile string) error {
60+
m, err := govecamole.New(256, 256)
61+
if err != nil {
62+
return err
63+
}
64+
// note: not deferring m.Close() here, as we want ownership passed to
65+
// the goroutine. this probably isn't the best way of doing this, so
66+
// TODO: figure out how to do this in a nicer way, perhaps on the
67+
// goroutine itself?
68+
69+
err = m.LoadFile(configFile, true, configFile)
70+
if err != nil {
71+
m.Close()
72+
return err
73+
}
74+
75+
go renderLoop(ch, m)
76+
77+
return nil
78+
}
79+
80+
// setupVecMaps sets up numProcs mapnik objects and spawns goroutines to handle
81+
// creating vector tiles for each of them. this returns a channel to write
82+
// requests to.
83+
func setupVecMaps(configFile string, numProcs int) (chan<- request, error) {
84+
err := govecamole.RegisterDefaultDatasources()
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
ch := make(chan request)
90+
91+
for i := 0; i < numProcs; i++ {
92+
err = startRenderer(ch, configFile)
93+
if err != nil {
94+
close(ch)
95+
return nil, err
96+
}
97+
}
98+
99+
return ch, nil
100+
}
101+
102+
// handleRequest creates a request and sends a request to a mapnik object and
103+
// handles writing the response (tile or error) back to the client.
104+
func handleRequest(z, x, y int, pool chan<- request, writer http.ResponseWriter) error {
105+
ch := make(chan response)
106+
req := request{z: z, x: x, y: y, reply: ch}
107+
pool <- req
108+
res := <-ch
109+
if res.err != nil {
110+
return res.err
111+
}
112+
_, err := writer.Write(res.tile)
113+
return err
114+
}
115+
116+
// parsePath parses a z/x/y.fmt path string into z, x & y components.
117+
func parsePath(p string) (int, int, int, error) {
118+
var z, x, y int
119+
var f string
120+
121+
n, err := fmt.Sscanf(p, "/%d/%d/%d.%s", &z, &x, &y, &f)
122+
if err != nil {
123+
return 0, 0, 0, err
124+
}
125+
126+
if n != 4 {
127+
return -1, 0, 0, errors.New("Expecting a path of format /z/x/y.fmt, but didn't match it.")
128+
}
129+
130+
if z < 0 {
131+
return -1, 0, 0, errors.New("Zoom level must be non-negative.")
132+
}
133+
134+
if z > 30 {
135+
return -1, 0, 0, errors.New("Zoom levels > 30 are not supported.")
136+
}
137+
138+
if x < 0 {
139+
return -1, 0, 0, errors.New("X coordinate must be non-negative.")
140+
}
141+
142+
if y < 0 {
143+
return -1, 0, 0, errors.New("Y coordinate must be non-negative.")
144+
}
145+
146+
maxcoord := 1 << uint(z)
147+
if x >= maxcoord {
148+
return -1, 0, 0, errors.New("X coordinate out of range at this zoom.")
149+
}
150+
151+
if y >= maxcoord {
152+
return -1, 0, 0, errors.New("Y coordinate out of range at this zoom.")
153+
}
154+
155+
return z, x, y, nil
156+
}
157+
158+
type vecMapsHandler struct {
159+
pool chan<- request
160+
}
161+
162+
func (self *vecMapsHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
163+
z, x, y, err := parsePath(req.URL.Path)
164+
if err != nil {
165+
http.Error(rw, err.Error(), http.StatusNotFound)
166+
return
167+
}
168+
169+
err = handleRequest(z, x, y, self.pool, rw)
170+
if err != nil {
171+
http.Error(rw, err.Error(), http.StatusInternalServerError)
172+
return
173+
}
174+
}
175+
176+
func (self *vecMapsHandler) Close() {
177+
close(self.pool)
178+
}
179+
180+
func NewVecMapsHandler(configFile string, numProcs int) (*vecMapsHandler, error) {
181+
ch, err := setupVecMaps(configFile, numProcs)
182+
if err != nil {
183+
return nil, err
184+
}
185+
186+
h := new(vecMapsHandler)
187+
h.pool = ch
188+
return h, nil
189+
}

0 commit comments

Comments
 (0)