1
0
Fork 0
mirror of https://github.com/Luzifer/staticmap.git synced 2025-01-20 11:31:56 +00:00

Update vendored libraries

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-06-17 14:59:52 +02:00
parent 2c2b9269d0
commit 3657b6663d
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
165 changed files with 21402 additions and 5650 deletions

50
Gopkg.lock generated
View file

@ -2,10 +2,10 @@
[[projects]]
branch = "allow-disable-of-attribution-rendering"
branch = "master"
name = "github.com/Luzifer/go-staticmaps"
packages = ["."]
revision = "d701c2c232ad9cbcac3e48e2c34e98544191c2fe"
revision = "320790ed53294a789e715b3d0d5da8110efea1a2"
[[projects]]
name = "github.com/Luzifer/go_helpers"
@ -14,8 +14,8 @@
"http",
"str"
]
revision = "15199b8e33ca5558e8c58af7924083983eb63ca4"
version = "v2.4.0"
revision = "b0da2aa67ecc05ee4c8848d679b4a11a2fa578b2"
version = "v2.6.0"
[[projects]]
name = "github.com/Luzifer/rconfig"
@ -23,12 +23,6 @@
revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e"
version = "v1.2.0"
[[projects]]
name = "github.com/Sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
branch = "master"
name = "github.com/Wessie/appdirs"
@ -77,25 +71,25 @@
"s1",
"s2"
]
revision = "fb250ae94fbe10f86b4f1a9b70a19925da3410b9"
revision = "e41ca803f92c4c1770133cfa5b4fc8249a7dbe82"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "53c1911da2b537f792e7cafcb446b05ffe33b996"
version = "v1.6.1"
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
branch = "master"
name = "github.com/lucasb-eyer/go-colorful"
packages = ["."]
revision = "231272389856c976b7500c4fffcc52ddf06ff4eb"
revision = "d9cec903b20cbeda6062366e460c2c1bdc717e4d"
[[projects]]
name = "github.com/patrickmn/go-cache"
@ -103,11 +97,17 @@
revision = "a3647f8e31d79543b2d0f0ae2fe5c379d72cedc0"
version = "v2.1.0"
[[projects]]
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
branch = "master"
@ -119,7 +119,7 @@
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "b2aa35443fbc700ab74c586ae79b81c171851023"
revision = "027cca12c2d63e3d62b670d901e8a2c95854feec"
[[projects]]
branch = "master"
@ -130,13 +130,13 @@
"font/plan9font",
"math/fixed"
]
revision = "f315e440302883054d0c2bd85486878cb4f8572c"
revision = "af66defab954cb421ca110193eed9477c8541e2a"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context"]
revision = "b68f30494add4df6bd8ef5e82803f308e7f7c59c"
revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196"
[[projects]]
branch = "master"
@ -145,19 +145,19 @@
"unix",
"windows"
]
revision = "378d26f46672a356c46195c28f61bdb4c0a781dd"
revision = "6c888cc515d3ed83fc103cf1d84468aad274b0a7"
[[projects]]
branch = "master"
name = "golang.org/x/time"
packages = ["rate"]
revision = "26559e0f760e39c24d730d3224364aef164ee23f"
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
[[projects]]
branch = "v2"
name = "gopkg.in/validator.v2"
packages = ["."]
revision = "59c90c7046f643cbe0d4e7c8776c42a84ce75910"
revision = "135c24b11c19e52befcae2ec3fca5d9b78c4e98e"
[[projects]]
name = "gopkg.in/yaml.v2"
@ -168,6 +168,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "bc8b02eb09921c3dcef7328c41ec92c8442d151672053769293e0c476f4a2188"
inputs-digest = "c59f72846eca4898dab7e7c3fc9a28b340f42aafbe4ac4866ad1ae33c382ea76"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -26,7 +26,7 @@
[[constraint]]
branch = "allow-disable-of-attribution-rendering"
branch = "master"
name = "github.com/Luzifer/go-staticmaps"
[[constraint]]
@ -38,7 +38,7 @@
version = "1.2.0"
[[constraint]]
name = "github.com/Sirupsen/logrus"
name = "github.com/sirupsen/logrus"
version = "1.0.5"
[[constraint]]

View file

@ -11,12 +11,12 @@ import (
httpHelper "github.com/Luzifer/go_helpers/http"
"github.com/Luzifer/rconfig"
log "github.com/Sirupsen/logrus"
"github.com/didip/tollbooth"
"github.com/didip/tollbooth/limiter"
"github.com/golang/geo/s2"
"github.com/gorilla/mux"
colorful "github.com/lucasb-eyer/go-colorful"
log "github.com/sirupsen/logrus"
)
var (

2
map.go
View file

@ -52,7 +52,7 @@ func generateMap(center s2.LatLng, zoom int, marker []marker, x, y int, disableA
ctx.SetZoom(zoom)
if disableAttribution {
ctx.ForceNoAttribution()
ctx.OverrideAttribution("")
}
if marker != nil {

View file

@ -43,7 +43,7 @@ type Context struct {
userAgent string
tileProvider *TileProvider
forceNoAttribution bool
overrideAttribution *string
}
// NewContext creates a new instance of Context
@ -149,12 +149,12 @@ func (m *Context) ClearOverlays() {
m.overlays = nil
}
// ForceNoAttribution disables attribution rendering
// OverrideAttribution sets a custom attribution string (or none if empty)
//
// Pay attention you might be violating the terms of usage for the
// selected map provider - only use the function if you are aware of this!
func (m *Context) ForceNoAttribution() {
m.forceNoAttribution = true
func (m *Context) OverrideAttribution(attribution string) {
m.overrideAttribution = &attribution
}
func (m *Context) determineBounds() s2.Rect {
@ -390,18 +390,23 @@ func (m *Context) Render() (image.Image, error) {
img, image.Point{trans.pCenterX - int(m.width)/2, trans.pCenterY - int(m.height)/2},
draw.Src)
attribution := m.tileProvider.Attribution
if m.overrideAttribution != nil {
attribution = *m.overrideAttribution
}
// draw attribution
if m.tileProvider.Attribution == "" || m.forceNoAttribution {
if attribution == "" {
return croppedImg, nil
}
_, textHeight := gc.MeasureString(m.tileProvider.Attribution)
_, textHeight := gc.MeasureString(attribution)
boxHeight := textHeight + 4.0
gc = gg.NewContextForRGBA(croppedImg)
gc.SetRGBA(0.0, 0.0, 0.0, 0.5)
gc.DrawRectangle(0.0, float64(m.height)-boxHeight, float64(m.width), boxHeight)
gc.Fill()
gc.SetRGBA(1.0, 1.0, 1.0, 0.75)
gc.DrawString(m.tileProvider.Attribution, 4.0, float64(m.height)-4.0)
gc.DrawString(attribution, 4.0, float64(m.height)-4.0)
return croppedImg, nil
}

133
vendor/github.com/golang/geo/s2/centroids.go generated vendored Normal file
View file

@ -0,0 +1,133 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s2
import (
"math"
"github.com/golang/geo/r3"
)
// There are several notions of the "centroid" of a triangle. First, there
// is the planar centroid, which is simply the centroid of the ordinary
// (non-spherical) triangle defined by the three vertices. Second, there is
// the surface centroid, which is defined as the intersection of the three
// medians of the spherical triangle. It is possible to show that this
// point is simply the planar centroid projected to the surface of the
// sphere. Finally, there is the true centroid (mass centroid), which is
// defined as the surface integral over the spherical triangle of (x,y,z)
// divided by the triangle area. This is the point that the triangle would
// rotate around if it was spinning in empty space.
//
// The best centroid for most purposes is the true centroid. Unlike the
// planar and surface centroids, the true centroid behaves linearly as
// regions are added or subtracted. That is, if you split a triangle into
// pieces and compute the average of their centroids (weighted by triangle
// area), the result equals the centroid of the original triangle. This is
// not true of the other centroids.
//
// Also note that the surface centroid may be nowhere near the intuitive
// "center" of a spherical triangle. For example, consider the triangle
// with vertices A=(1,eps,0), B=(0,0,1), C=(-1,eps,0) (a quarter-sphere).
// The surface centroid of this triangle is at S=(0, 2*eps, 1), which is
// within a distance of 2*eps of the vertex B. Note that the median from A
// (the segment connecting A to the midpoint of BC) passes through S, since
// this is the shortest path connecting the two endpoints. On the other
// hand, the true centroid is at M=(0, 0.5, 0.5), which when projected onto
// the surface is a much more reasonable interpretation of the "center" of
// this triangle.
//
// TrueCentroid returns the true centroid of the spherical triangle ABC
// multiplied by the signed area of spherical triangle ABC. The reasons for
// multiplying by the signed area are (1) this is the quantity that needs to be
// summed to compute the centroid of a union or difference of triangles, and
// (2) it's actually easier to calculate this way. All points must have unit length.
//
// Note that the result of this function is defined to be Point(0, 0, 0) if
// the triangle is degenerate.
func TrueCentroid(a, b, c Point) Point {
// Use Distance to get accurate results for small triangles.
ra := float64(1)
if sa := float64(b.Distance(c)); sa != 0 {
ra = sa / math.Sin(sa)
}
rb := float64(1)
if sb := float64(c.Distance(a)); sb != 0 {
rb = sb / math.Sin(sb)
}
rc := float64(1)
if sc := float64(a.Distance(b)); sc != 0 {
rc = sc / math.Sin(sc)
}
// Now compute a point M such that:
//
// [Ax Ay Az] [Mx] [ra]
// [Bx By Bz] [My] = 0.5 * det(A,B,C) * [rb]
// [Cx Cy Cz] [Mz] [rc]
//
// To improve the numerical stability we subtract the first row (A) from the
// other two rows; this reduces the cancellation error when A, B, and C are
// very close together. Then we solve it using Cramer's rule.
//
// The result is the true centroid of the triangle multiplied by the
// triangle's area.
//
// This code still isn't as numerically stable as it could be.
// The biggest potential improvement is to compute B-A and C-A more
// accurately so that (B-A)x(C-A) is always inside triangle ABC.
x := r3.Vector{a.X, b.X - a.X, c.X - a.X}
y := r3.Vector{a.Y, b.Y - a.Y, c.Y - a.Y}
z := r3.Vector{a.Z, b.Z - a.Z, c.Z - a.Z}
r := r3.Vector{ra, rb - ra, rc - ra}
return Point{r3.Vector{y.Cross(z).Dot(r), z.Cross(x).Dot(r), x.Cross(y).Dot(r)}.Mul(0.5)}
}
// EdgeTrueCentroid returns the true centroid of the spherical geodesic edge AB
// multiplied by the length of the edge AB. As with triangles, the true centroid
// of a collection of line segments may be computed simply by summing the result
// of this method for each segment.
//
// Note that the planar centroid of a line segment is simply 0.5 * (a + b),
// while the surface centroid is (a + b).Normalize(). However neither of
// these values is appropriate for computing the centroid of a collection of
// edges (such as a polyline).
//
// Also note that the result of this function is defined to be Point(0, 0, 0)
// if the edge is degenerate.
func EdgeTrueCentroid(a, b Point) Point {
// The centroid (multiplied by length) is a vector toward the midpoint
// of the edge, whose length is twice the sine of half the angle between
// the two vertices. Defining theta to be this angle, we have:
vDiff := a.Sub(b.Vector) // Length == 2*sin(theta)
vSum := a.Add(b.Vector) // Length == 2*cos(theta)
sin2 := vDiff.Norm2()
cos2 := vSum.Norm2()
if cos2 == 0 {
return Point{} // Ignore antipodal edges.
}
return Point{vSum.Mul(math.Sqrt(sin2 / cos2))} // Length == 2*sin(theta)
}
// PlanarCentroid returns the centroid of the planar triangle ABC. This can be
// normalized to unit length to obtain the "surface centroid" of the corresponding
// spherical triangle, i.e. the intersection of the three medians. However, note
// that for large spherical triangles the surface centroid may be nowhere near
// the intuitive "center".
func PlanarCentroid(a, b, c Point) Point {
return Point{a.Add(b.Vector).Add(c.Vector).Mul(1. / 3)}
}

157
vendor/github.com/golang/geo/s2/contains_point_query.go generated vendored Normal file
View file

@ -0,0 +1,157 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s2
// VertexModel defines whether shapes are considered to contain their vertices.
// Note that these definitions differ from the ones used by BooleanOperation.
//
// Note that points other than vertices are never contained by polylines.
// If you want need this behavior, use ClosestEdgeQuery's IsDistanceLess
// with a suitable distance threshold instead.
type VertexModel int
const (
// VertexModelOpen means no shapes contain their vertices (not even
// points). Therefore Contains(Point) returns true if and only if the
// point is in the interior of some polygon.
VertexModelOpen VertexModel = iota
// VertexModelSemiOpen means that polygon point containment is defined
// such that if several polygons tile the region around a vertex, then
// exactly one of those polygons contains that vertex. Points and
// polylines still do not contain any vertices.
VertexModelSemiOpen
// VertexModelClosed means all shapes contain their vertices (including
// points and polylines).
VertexModelClosed
)
// ContainsPointQuery determines whether one or more shapes in a ShapeIndex
// contain a given Point. The ShapeIndex may contain any number of points,
// polylines, and/or polygons (possibly overlapping). Shape boundaries may be
// modeled as Open, SemiOpen, or Closed (this affects whether or not shapes are
// considered to contain their vertices).
//
// Note that if you need to do a large number of point containment
// tests, it is more efficient to re-use the query rather than creating a new
// one each time.
type ContainsPointQuery struct {
model VertexModel
index *ShapeIndex
iter *ShapeIndexIterator
}
// NewContainsPointQuery creates a new instance of the ContainsPointQuery for the index
// and given vertex model choice.
func NewContainsPointQuery(index *ShapeIndex, model VertexModel) *ContainsPointQuery {
return &ContainsPointQuery{
index: index,
model: model,
iter: index.Iterator(),
}
}
// Contains reports whether any shape in the queries index contains the point p
// under the queries vertex model (Open, SemiOpen, or Closed).
func (q *ContainsPointQuery) Contains(p Point) bool {
if !q.iter.LocatePoint(p) {
return false
}
cell := q.iter.IndexCell()
for _, clipped := range cell.shapes {
if q.shapeContains(clipped, q.iter.Center(), p) {
return true
}
}
return false
}
// shapeContains reports whether the clippedShape from the iterator's center position contains
// the given point.
func (q *ContainsPointQuery) shapeContains(clipped *clippedShape, center, p Point) bool {
inside := clipped.containsCenter
numEdges := clipped.numEdges()
if numEdges <= 0 {
return inside
}
shape := q.index.Shape(clipped.shapeID)
if !shape.HasInterior() {
// Points and polylines can be ignored unless the vertex model is Closed.
if q.model != VertexModelClosed {
return false
}
// Otherwise, the point is contained if and only if it matches a vertex.
for _, edgeID := range clipped.edges {
edge := shape.Edge(edgeID)
if edge.V0 == p || edge.V1 == p {
return true
}
}
return false
}
// Test containment by drawing a line segment from the cell center to the
// given point and counting edge crossings.
crosser := NewEdgeCrosser(center, p)
for _, edgeID := range clipped.edges {
edge := shape.Edge(edgeID)
sign := crosser.CrossingSign(edge.V0, edge.V1)
if sign == DoNotCross {
continue
}
if sign == MaybeCross {
// For the Open and Closed models, check whether p is a vertex.
if q.model != VertexModelSemiOpen && (edge.V0 == p || edge.V1 == p) {
return (q.model == VertexModelClosed)
}
// C++ plays fast and loose with the int <-> bool conversions here.
if VertexCrossing(crosser.a, crosser.b, edge.V0, edge.V1) {
sign = Cross
} else {
sign = DoNotCross
}
}
inside = inside != (sign == Cross)
}
return inside
}
// ShapeContains reports whether the given shape contains the point under this
// queries vertex model (Open, SemiOpen, or Closed).
//
// This requires the shape belongs to this queries index.
func (q *ContainsPointQuery) ShapeContains(shape Shape, p Point) bool {
if !q.iter.LocatePoint(p) {
return false
}
clipped := q.iter.IndexCell().findByShapeID(q.index.idForShape(shape))
if clipped == nil {
return false
}
return q.shapeContains(clipped, q.iter.Center(), p)
}
// TODO(roberts): Remaining methods from C++
// func (q *ContainsPointQuery) ContainingShapes(p Point) []Shape
// type shapeVisitorFunc func(shape Shape) bool
// func (q *ContainsPointQuery) VisitContainingShapes(p Point, v shapeVisitorFunc) bool
// type edgeVisitorFunc func(shape ShapeEdge) bool
// func (q *ContainsPointQuery) VisitIncidentEdges(p Point, v edgeVisitorFunc) bool

View file

@ -203,21 +203,16 @@ func (l *Loop) initBound() {
l.subregionBound = ExpandForSubregions(l.bound)
}
// IsValid reports whether this is a valid loop or not.
func (l *Loop) IsValid() bool {
return l.findValidationError() == nil
}
// findValidationError reports whether this is not a valid loop and if so
// returns an error describing why. This function requires the Loops ShapeIndex
// to have been intialized.
func (l *Loop) findValidationError() error {
// Validate checks whether this is a valid loop.
func (l *Loop) Validate() error {
if err := l.findValidationErrorNoIndex(); err != nil {
return err
}
// Check for intersections between non-adjacent edges (including at vertices)
// TODO(roberts): Once shapeutil gets findAnyCrossing uncomment this.
// return findAnyCrossing(l.index)
return nil
}
@ -476,7 +471,7 @@ func (l *Loop) Edge(i int) Edge {
// NumChains reports the number of contiguous edge chains in the Loop.
func (l *Loop) NumChains() int {
if l.isEmptyOrFull() {
if l.IsEmpty() {
return 0
}
return 1
@ -501,12 +496,12 @@ func (l *Loop) ChainPosition(edgeID int) ChainPosition {
// dimension returns the dimension of the geometry represented by this Loop.
func (l *Loop) dimension() dimension { return polygonGeometry }
// IsEmpty reports true if this is the special "empty" loop that contains no points.
// IsEmpty reports true if this is the special empty loop that contains no points.
func (l *Loop) IsEmpty() bool {
return l.isEmptyOrFull() && !l.ContainsOrigin()
}
// IsFull reports true if this is the special "full" loop that contains all points.
// IsFull reports true if this is the special full loop that contains all points.
func (l *Loop) IsFull() bool {
return l.isEmptyOrFull() && l.ContainsOrigin()
}

View file

@ -131,163 +131,6 @@ func (p Point) ApproxEqual(other Point) bool {
return p.Vector.Angle(other.Vector) <= s1.Angle(epsilon)
}
// PointArea returns the area on the unit sphere for the triangle defined by the
// given points.
//
// This method is based on l'Huilier's theorem,
//
// tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2))
//
// where E is the spherical excess of the triangle (i.e. its area),
// a, b, c are the side lengths, and
// s is the semiperimeter (a + b + c) / 2.
//
// The only significant source of error using l'Huilier's method is the
// cancellation error of the terms (s-a), (s-b), (s-c). This leads to a
// *relative* error of about 1e-16 * s / min(s-a, s-b, s-c). This compares
// to a relative error of about 1e-15 / E using Girard's formula, where E is
// the true area of the triangle. Girard's formula can be even worse than
// this for very small triangles, e.g. a triangle with a true area of 1e-30
// might evaluate to 1e-5.
//
// So, we prefer l'Huilier's formula unless dmin < s * (0.1 * E), where
// dmin = min(s-a, s-b, s-c). This basically includes all triangles
// except for extremely long and skinny ones.
//
// Since we don't know E, we would like a conservative upper bound on
// the triangle area in terms of s and dmin. It's possible to show that
// E <= k1 * s * sqrt(s * dmin), where k1 = 2*sqrt(3)/Pi (about 1).
// Using this, it's easy to show that we should always use l'Huilier's
// method if dmin >= k2 * s^5, where k2 is about 1e-2. Furthermore,
// if dmin < k2 * s^5, the triangle area is at most k3 * s^4, where
// k3 is about 0.1. Since the best case error using Girard's formula
// is about 1e-15, this means that we shouldn't even consider it unless
// s >= 3e-4 or so.
func PointArea(a, b, c Point) float64 {
sa := float64(b.Angle(c.Vector))
sb := float64(c.Angle(a.Vector))
sc := float64(a.Angle(b.Vector))
s := 0.5 * (sa + sb + sc)
if s >= 3e-4 {
// Consider whether Girard's formula might be more accurate.
dmin := s - math.Max(sa, math.Max(sb, sc))
if dmin < 1e-2*s*s*s*s*s {
// This triangle is skinny enough to use Girard's formula.
area := GirardArea(a, b, c)
if dmin < s*0.1*area {
return area
}
}
}
// Use l'Huilier's formula.
return 4 * math.Atan(math.Sqrt(math.Max(0.0, math.Tan(0.5*s)*math.Tan(0.5*(s-sa))*
math.Tan(0.5*(s-sb))*math.Tan(0.5*(s-sc)))))
}
// GirardArea returns the area of the triangle computed using Girard's formula.
// All points should be unit length, and no two points should be antipodal.
//
// This method is about twice as fast as PointArea() but has poor relative
// accuracy for small triangles. The maximum error is about 5e-15 (about
// 0.25 square meters on the Earth's surface) and the average error is about
// 1e-15. These bounds apply to triangles of any size, even as the maximum
// edge length of the triangle approaches 180 degrees. But note that for
// such triangles, tiny perturbations of the input points can change the
// true mathematical area dramatically.
func GirardArea(a, b, c Point) float64 {
// This is equivalent to the usual Girard's formula but is slightly more
// accurate, faster to compute, and handles a == b == c without a special
// case. PointCross is necessary to get good accuracy when two of
// the input points are very close together.
ab := a.PointCross(b)
bc := b.PointCross(c)
ac := a.PointCross(c)
area := float64(ab.Angle(ac.Vector) - ab.Angle(bc.Vector) + bc.Angle(ac.Vector))
if area < 0 {
area = 0
}
return area
}
// SignedArea returns a positive value for counterclockwise triangles and a negative
// value otherwise (similar to PointArea).
func SignedArea(a, b, c Point) float64 {
return float64(RobustSign(a, b, c)) * PointArea(a, b, c)
}
// TrueCentroid returns the true centroid of the spherical triangle ABC multiplied by the
// signed area of spherical triangle ABC. The result is not normalized.
// The reasons for multiplying by the signed area are (1) this is the quantity
// that needs to be summed to compute the centroid of a union or difference of triangles,
// and (2) it's actually easier to calculate this way. All points must have unit length.
//
// The true centroid (mass centroid) is defined as the surface integral
// over the spherical triangle of (x,y,z) divided by the triangle area.
// This is the point that the triangle would rotate around if it was
// spinning in empty space.
//
// The best centroid for most purposes is the true centroid. Unlike the
// planar and surface centroids, the true centroid behaves linearly as
// regions are added or subtracted. That is, if you split a triangle into
// pieces and compute the average of their centroids (weighted by triangle
// area), the result equals the centroid of the original triangle. This is
// not true of the other centroids.
func TrueCentroid(a, b, c Point) Point {
ra := float64(1)
if sa := float64(b.Distance(c)); sa != 0 {
ra = sa / math.Sin(sa)
}
rb := float64(1)
if sb := float64(c.Distance(a)); sb != 0 {
rb = sb / math.Sin(sb)
}
rc := float64(1)
if sc := float64(a.Distance(b)); sc != 0 {
rc = sc / math.Sin(sc)
}
// Now compute a point M such that:
//
// [Ax Ay Az] [Mx] [ra]
// [Bx By Bz] [My] = 0.5 * det(A,B,C) * [rb]
// [Cx Cy Cz] [Mz] [rc]
//
// To improve the numerical stability we subtract the first row (A) from the
// other two rows; this reduces the cancellation error when A, B, and C are
// very close together. Then we solve it using Cramer's rule.
//
// This code still isn't as numerically stable as it could be.
// The biggest potential improvement is to compute B-A and C-A more
// accurately so that (B-A)x(C-A) is always inside triangle ABC.
x := r3.Vector{a.X, b.X - a.X, c.X - a.X}
y := r3.Vector{a.Y, b.Y - a.Y, c.Y - a.Y}
z := r3.Vector{a.Z, b.Z - a.Z, c.Z - a.Z}
r := r3.Vector{ra, rb - ra, rc - ra}
return Point{r3.Vector{y.Cross(z).Dot(r), z.Cross(x).Dot(r), x.Cross(y).Dot(r)}.Mul(0.5)}
}
// PlanarCentroid returns the centroid of the planar triangle ABC, which is not normalized.
// It can be normalized to unit length to obtain the "surface centroid" of the corresponding
// spherical triangle, i.e. the intersection of the three medians. However,
// note that for large spherical triangles the surface centroid may be
// nowhere near the intuitive "center" (see example in TrueCentroid comments).
//
// Note that the surface centroid may be nowhere near the intuitive
// "center" of a spherical triangle. For example, consider the triangle
// with vertices A=(1,eps,0), B=(0,0,1), C=(-1,eps,0) (a quarter-sphere).
// The surface centroid of this triangle is at S=(0, 2*eps, 1), which is
// within a distance of 2*eps of the vertex B. Note that the median from A
// (the segment connecting A to the midpoint of BC) passes through S, since
// this is the shortest path connecting the two endpoints. On the other
// hand, the true centroid is at M=(0, 0.5, 0.5), which when projected onto
// the surface is a much more reasonable interpretation of the "center" of
// this triangle.
func PlanarCentroid(a, b, c Point) Point {
return Point{a.Add(b.Vector).Add(c.Vector).Mul(1. / 3)}
}
// ChordAngleBetweenPoints constructs a ChordAngle corresponding to the distance
// between the two given points. The points must be unit length.
func ChordAngleBetweenPoints(x, y Point) s1.ChordAngle {
@ -392,39 +235,6 @@ func (p *Point) decode(d *decoder) {
p.Z = d.readFloat64()
}
// Angle returns the interior angle at the vertex B in the triangle ABC. The
// return value is always in the range [0, pi]. All points should be
// normalized. Ensures that Angle(a,b,c) == Angle(c,b,a) for all a,b,c.
//
// The angle is undefined if A or C is diametrically opposite from B, and
// becomes numerically unstable as the length of edge AB or BC approaches
// 180 degrees.
func Angle(a, b, c Point) s1.Angle {
return a.PointCross(b).Angle(c.PointCross(b).Vector)
}
// TurnAngle returns the exterior angle at vertex B in the triangle ABC. The
// return value is positive if ABC is counterclockwise and negative otherwise.
// If you imagine an ant walking from A to B to C, this is the angle that the
// ant turns at vertex B (positive = left = CCW, negative = right = CW).
// This quantity is also known as the "geodesic curvature" at B.
//
// Ensures that TurnAngle(a,b,c) == -TurnAngle(c,b,a) for all distinct
// a,b,c. The result is undefined if (a == b || b == c), but is either
// -Pi or Pi if (a == c). All points should be normalized.
func TurnAngle(a, b, c Point) s1.Angle {
// We use PointCross to get good accuracy when two points are very
// close together, and RobustSign to ensure that the sign is correct for
// turns that are close to 180 degrees.
angle := a.PointCross(b).Angle(b.PointCross(c).Vector)
// Don't return RobustSign * angle because it is legal to have (a == c).
if RobustSign(a, b, c) == CounterClockwise {
return angle
}
return -angle
}
// Rotate the given point about the given axis by the given angle. p and
// axis must be unit length; angle has no restrictions (e.g., it can be
// positive, negative, greater than 360 degrees, etc).

149
vendor/github.com/golang/geo/s2/point_measures.go generated vendored Normal file
View file

@ -0,0 +1,149 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s2
import (
"math"
"github.com/golang/geo/s1"
)
// PointArea returns the area of triangle ABC. This method combines two different
// algorithms to get accurate results for both large and small triangles.
// The maximum error is about 5e-15 (about 0.25 square meters on the Earth's
// surface), the same as GirardArea below, but unlike that method it is
// also accurate for small triangles. Example: when the true area is 100
// square meters, PointArea yields an error about 1 trillion times smaller than
// GirardArea.
//
// All points should be unit length, and no two points should be antipodal.
// The area is always positive.
func PointArea(a, b, c Point) float64 {
// This method is based on l'Huilier's theorem,
//
// tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2))
//
// where E is the spherical excess of the triangle (i.e. its area),
// a, b, c are the side lengths, and
// s is the semiperimeter (a + b + c) / 2.
//
// The only significant source of error using l'Huilier's method is the
// cancellation error of the terms (s-a), (s-b), (s-c). This leads to a
// *relative* error of about 1e-16 * s / min(s-a, s-b, s-c). This compares
// to a relative error of about 1e-15 / E using Girard's formula, where E is
// the true area of the triangle. Girard's formula can be even worse than
// this for very small triangles, e.g. a triangle with a true area of 1e-30
// might evaluate to 1e-5.
//
// So, we prefer l'Huilier's formula unless dmin < s * (0.1 * E), where
// dmin = min(s-a, s-b, s-c). This basically includes all triangles
// except for extremely long and skinny ones.
//
// Since we don't know E, we would like a conservative upper bound on
// the triangle area in terms of s and dmin. It's possible to show that
// E <= k1 * s * sqrt(s * dmin), where k1 = 2*sqrt(3)/Pi (about 1).
// Using this, it's easy to show that we should always use l'Huilier's
// method if dmin >= k2 * s^5, where k2 is about 1e-2. Furthermore,
// if dmin < k2 * s^5, the triangle area is at most k3 * s^4, where
// k3 is about 0.1. Since the best case error using Girard's formula
// is about 1e-15, this means that we shouldn't even consider it unless
// s >= 3e-4 or so.
sa := float64(b.Angle(c.Vector))
sb := float64(c.Angle(a.Vector))
sc := float64(a.Angle(b.Vector))
s := 0.5 * (sa + sb + sc)
if s >= 3e-4 {
// Consider whether Girard's formula might be more accurate.
dmin := s - math.Max(sa, math.Max(sb, sc))
if dmin < 1e-2*s*s*s*s*s {
// This triangle is skinny enough to use Girard's formula.
area := GirardArea(a, b, c)
if dmin < s*0.1*area {
return area
}
}
}
// Use l'Huilier's formula.
return 4 * math.Atan(math.Sqrt(math.Max(0.0, math.Tan(0.5*s)*math.Tan(0.5*(s-sa))*
math.Tan(0.5*(s-sb))*math.Tan(0.5*(s-sc)))))
}
// GirardArea returns the area of the triangle computed using Girard's formula.
// All points should be unit length, and no two points should be antipodal.
//
// This method is about twice as fast as PointArea() but has poor relative
// accuracy for small triangles. The maximum error is about 5e-15 (about
// 0.25 square meters on the Earth's surface) and the average error is about
// 1e-15. These bounds apply to triangles of any size, even as the maximum
// edge length of the triangle approaches 180 degrees. But note that for
// such triangles, tiny perturbations of the input points can change the
// true mathematical area dramatically.
func GirardArea(a, b, c Point) float64 {
// This is equivalent to the usual Girard's formula but is slightly more
// accurate, faster to compute, and handles a == b == c without a special
// case. PointCross is necessary to get good accuracy when two of
// the input points are very close together.
ab := a.PointCross(b)
bc := b.PointCross(c)
ac := a.PointCross(c)
area := float64(ab.Angle(ac.Vector) - ab.Angle(bc.Vector) + bc.Angle(ac.Vector))
if area < 0 {
area = 0
}
return area
}
// SignedArea returns a positive value for counterclockwise triangles and a negative
// value otherwise (similar to PointArea).
func SignedArea(a, b, c Point) float64 {
return float64(RobustSign(a, b, c)) * PointArea(a, b, c)
}
// Angle returns the interior angle at the vertex B in the triangle ABC. The
// return value is always in the range [0, pi]. All points should be
// normalized. Ensures that Angle(a,b,c) == Angle(c,b,a) for all a,b,c.
//
// The angle is undefined if A or C is diametrically opposite from B, and
// becomes numerically unstable as the length of edge AB or BC approaches
// 180 degrees.
func Angle(a, b, c Point) s1.Angle {
// PointCross is necessary to get good accuracy when two of the input
// points are very close together.
return a.PointCross(b).Angle(c.PointCross(b).Vector)
}
// TurnAngle returns the exterior angle at vertex B in the triangle ABC. The
// return value is positive if ABC is counterclockwise and negative otherwise.
// If you imagine an ant walking from A to B to C, this is the angle that the
// ant turns at vertex B (positive = left = CCW, negative = right = CW).
// This quantity is also known as the "geodesic curvature" at B.
//
// Ensures that TurnAngle(a,b,c) == -TurnAngle(c,b,a) for all distinct
// a,b,c. The result is undefined if (a == b || b == c), but is either
// -Pi or Pi if (a == c). All points should be normalized.
func TurnAngle(a, b, c Point) s1.Angle {
// We use PointCross to get good accuracy when two points are very
// close together, and RobustSign to ensure that the sign is correct for
// turns that are close to 180 degrees.
angle := a.PointCross(b).Angle(b.PointCross(c).Vector)
// Don't return RobustSign * angle because it is legal to have (a == c).
if RobustSign(a, b, c) == CounterClockwise {
return angle
}
return -angle
}

View file

@ -330,18 +330,9 @@ func FullPolygon() *Polygon {
return ret
}
// IsValid reports whether this is a valid polygon (including checking whether all
// the loops are themselves valid).
func (p *Polygon) IsValid() bool {
// TODO(roberts): If we want to expose the error, add a Validate() error method.
return p.validate() == nil
}
// validate reports any error if this is not a valid polygon.
//
// Note that in the returned error messages, loops that represent holes have their
// edges numbered in reverse order, starting from the last vertex of the loop.
func (p *Polygon) validate() error {
// Validate checks whether this is a valid polygon,
// including checking whether all the loops are themselves valid.
func (p *Polygon) Validate() error {
for i, l := range p.loops {
// Check for loop errors that don't require building a ShapeIndex.
if err := l.findValidationErrorNoIndex(); err != nil {
@ -671,10 +662,6 @@ func (p *Polygon) ReferencePoint() ReferencePoint {
// NumChains reports the number of contiguous edge chains in the Polygon.
func (p *Polygon) NumChains() int {
if p.IsFull() {
return 0
}
return p.NumLoops()
}
@ -687,7 +674,13 @@ func (p *Polygon) Chain(chainID int) Chain {
for j := 0; j < chainID; j++ {
e += len(p.Loop(j).vertices)
}
return Chain{e, len(p.Loop(chainID).vertices)}
// Polygon represents a full loop as a loop with one vertex, while
// Shape represents a full loop as a chain with no vertices.
if numVertices := p.Loop(chainID).NumVertices(); numVertices != 1 {
return Chain{e, numVertices}
}
return Chain{e, 0}
}
// ChainEdge returns the j-th edge of the i-th edge Chain (loop).
@ -906,6 +899,16 @@ func (p *Polygon) anyLoopIntersects(o *Loop) bool {
return false
}
// Area returns the area of the polygon interior, i.e. the region on the left side
// of an odd number of loops. The return value is between 0 and 4*Pi.
func (p *Polygon) Area() float64 {
var area float64
for _, loop := range p.loops {
area += float64(loop.Sign()) * loop.Area()
}
return area
}
// Encode encodes the Polygon
func (p *Polygon) Encode(w io.Writer) error {
e := &encoder{w: w}
@ -1087,7 +1090,6 @@ func (p *Polygon) decodeCompressed(d *decoder) {
}
// TODO(roberts): Differences from C++
// Area
// Centroid
// SnapLevel
// DistanceToPoint

View file

@ -197,6 +197,12 @@ func (p *Polyline) ChainPosition(edgeID int) ChainPosition {
// dimension returns the dimension of the geometry represented by this Polyline.
func (p *Polyline) dimension() dimension { return polylineGeometry }
// IsEmpty reports whether this shape contains no points.
func (p *Polyline) IsEmpty() bool { return defaultShapeIsEmpty(p) }
// IsFull reports whether this shape contains all points on the sphere.
func (p *Polyline) IsFull() bool { return defaultShapeIsFull(p) }
// findEndVertex reports the maximal end index such that the line segment between
// the start index and this one such that the line segment between these two
// vertices passes within the given tolerance of all interior vertices, in order.

53
vendor/github.com/golang/geo/s2/polyline_measures.go generated vendored Normal file
View file

@ -0,0 +1,53 @@
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s2
// This file defines various measures for polylines on the sphere. These are
// low-level methods that work directly with arrays of Points. They are used to
// implement the methods in various other measures files.
import (
"github.com/golang/geo/r3"
"github.com/golang/geo/s1"
)
// polylineLength returns the length of the given Polyline.
// It returns 0 for polylines with fewer than two vertices.
func polylineLength(p []Point) s1.Angle {
var length s1.Angle
for i := 1; i < len(p); i++ {
length += p[i-1].Distance(p[i])
}
return length
}
// polylineCentroid returns the true centroid of the polyline multiplied by the
// length of the polyline. The result is not unit length, so you may wish to
// normalize it.
//
// Scaling by the Polyline length makes it easy to compute the centroid
// of several Polylines (by simply adding up their centroids).
//
// Note that for degenerate Polylines (e.g., AA) this returns Point(0, 0, 0).
// (This answer is correct; the result of this function is a line integral over
// the polyline, whose value is always zero if the polyline is degenerate.)
func polylineCentroid(p []Point) Point {
var centroid r3.Vector
for i := 1; i < len(p); i++ {
centroid = centroid.Add(EdgeTrueCentroid(p[i-1], p[i]).Vector)
}
return Point{centroid}
}

View file

@ -59,6 +59,42 @@ func (e edges) Len() int { return len(e) }
func (e edges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (e edges) Less(i, j int) bool { return e[i].Cmp(e[j]) == -1 }
// ShapeEdgeID is a unique identifier for an Edge within an ShapeIndex,
// consisting of a (shapeID, edgeID) pair.
type ShapeEdgeID struct {
ShapeID int32
EdgeID int32
}
// Cmp compares the two ShapeEdgeIDs and returns
//
// -1 if s < other
// 0 if s == other
// +1 if s > other
//
// The two are compared first by shape id and then by edge id.
func (s ShapeEdgeID) Cmp(other ShapeEdgeID) int {
switch {
case s.ShapeID < other.ShapeID:
return -1
case s.ShapeID > other.ShapeID:
return 1
}
switch {
case s.EdgeID < other.EdgeID:
return -1
case s.EdgeID > other.EdgeID:
return 1
}
return 0
}
// ShapeEdge represents a ShapeEdgeID with the two endpoints of that Edge.
type ShapeEdge struct {
ID ShapeEdgeID
Edge Edge
}
// Chain represents a range of edge IDs corresponding to a chain of connected
// edges, specified as a (start, length) pair. The chain is defined to consist of
// edge IDs {start, start + 1, ..., start + length - 1}.
@ -179,11 +215,31 @@ type Shape interface {
// of edge chains where each chain represents one polygon loop.
// Polygons may have degeneracies (e.g., degenerate edges or sibling
// pairs consisting of an edge and its corresponding reversed edge).
// A polygon loop may also be full (containing all points on the
// sphere); by convention this is represented as a chain with no edges.
// (See laxPolygon for more details.)
//
// Note that this method allows degenerate geometry of different dimensions
// to be distinguished, e.g. it allows a point to be distinguished from a
// polyline or polygon that has been simplified to a single point.
dimension() dimension
// IsEmpty reports whether the Shape contains no points. (Note that the full
// polygon is represented as a chain with zero edges.)
IsEmpty() bool
// IsFull reports whether the Shape contains all points on the sphere.
IsFull() bool
}
// defaultShapeIsEmpty reports whether this shape contains no points.
func defaultShapeIsEmpty(s Shape) bool {
return s.NumEdges() == 0 && (!s.HasInterior() || s.NumChains() == 0)
}
// defaultShapeIsFull reports whether this shape contains all points on the sphere.
func defaultShapeIsFull(s Shape) bool {
return s.NumEdges() == 0 && s.HasInterior() && s.NumChains() > 0
}
// A minimal check for types that should satisfy the Shape interface.

View file

@ -684,6 +684,22 @@ func (s *ShapeIndex) NumEdges() int {
// Shape returns the shape with the given ID, or nil if the shape has been removed from the index.
func (s *ShapeIndex) Shape(id int32) Shape { return s.shapes[id] }
// idForShape returns the id of the given shape in this index, or -1 if it is
// not in the index.
//
// TODO(roberts): Need to figure out an appropriate way to expose this on a Shape.
// C++ allows a given S2 type (Loop, Polygon, etc) to be part of multiple indexes.
// By having each type extend S2Shape which has an id element, they all inherit their
// own id field rather than having to track it themselves.
func (s *ShapeIndex) idForShape(shape Shape) int32 {
for k, v := range s.shapes {
if v == shape {
return k
}
}
return -1
}
// Add adds the given shape to the index and returns the assigned ID..
func (s *ShapeIndex) Add(shape Shape) int32 {
s.shapes[s.nextID] = shape
@ -696,13 +712,7 @@ func (s *ShapeIndex) Add(shape Shape) int32 {
func (s *ShapeIndex) Remove(shape Shape) {
// The index updates itself lazily because it is much more efficient to
// process additions and removals in batches.
// Lookup the id of this shape in the index.
id := int32(-1)
for k, v := range s.shapes {
if v == shape {
id = k
}
}
id := s.idForShape(shape)
// If the shape wasn't found, it's already been removed or was not in the index.
if s.shapes[id] == nil {
@ -723,7 +733,7 @@ func (s *ShapeIndex) Remove(shape Shape) {
shapeID: id,
hasInterior: shape.HasInterior(),
containsTrackerOrigin: shape.ReferencePoint().Contained,
edges: make([]Edge, numEdges),
edges: make([]Edge, numEdges),
}
for e := 0; e < numEdges; e++ {

View file

@ -113,7 +113,7 @@ func (r *rangeIterator) refresh() {
func referencePointForShape(shape Shape) ReferencePoint {
if shape.NumEdges() == 0 {
// A shape with no edges is defined to be full if and only if it
// contains an empty loop.
// contains at least one chain.
return OriginReferencePoint(shape.NumChains() > 0)
}
// Define a "matched" edge as one that can be paired with a corresponding
@ -156,8 +156,9 @@ func referencePointForShape(shape Shape) ReferencePoint {
}
}
// All vertices are balanced, so this polygon is either empty or full. By
// convention it is defined to be full if it contains any empty loop.
// All vertices are balanced, so this polygon is either empty or full except
// for degeneracies. By convention it is defined to be full if it contains
// any chain with no edges.
for i := 0; i < shape.NumChains(); i++ {
if shape.Chain(i).Length == 0 {
return OriginReferencePoint(true)

View file

@ -7,13 +7,13 @@ matrix:
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
allow_failures:
- go: tip
install:
- go get golang.org/x/tools/cmd/vet
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go tool vet .
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

View file

@ -4,4 +4,7 @@ context
gorilla/context is a general purpose registry for global request variables.
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

View file

@ -5,6 +5,12 @@
/*
Package context stores values shared during a request lifetime.
Note: gorilla/context, having been born well before `context.Context` existed,
does not play well > with the shallow copying of the request that
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
(added to net/http Go 1.7 onwards) performs. You should either use *just*
gorilla/context, or moving forward, the new `http.Request.Context()`.
For example, a router can set variables extracted from the URL and later
application handlers can access those values, or it can be used to store
sessions values to be saved at the end of a request. There are several

View file

@ -3,11 +3,12 @@ sudo: false
matrix:
include:
- go: 1.5
- go: 1.6
- go: 1.7
- go: 1.8
- go: 1.9
- go: 1.5.x
- go: 1.6.x
- go: 1.7.x
- go: 1.8.x
- go: 1.9.x
- go: 1.10.x
- go: tip
allow_failures:
- go: tip

View file

@ -1,5 +1,5 @@
gorilla/mux
===
# gorilla/mux
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
@ -29,6 +29,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Walking Routes](#walking-routes)
* [Graceful Shutdown](#graceful-shutdown)
* [Middleware](#middleware)
* [Testing Handlers](#testing-handlers)
* [Full Example](#full-example)
---
@ -178,70 +179,13 @@ s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
### Listing Routes
Routes on a mux can be listed using the Router.Walk method—useful for generating documentation:
```go
package main
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
return
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/products", handler).Methods("POST")
r.HandleFunc("/articles", handler).Methods("GET")
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
t, err := route.GetPathTemplate()
if err != nil {
return err
}
qt, err := route.GetQueriesTemplates()
if err != nil {
return err
}
// p will contain regular expression is compatible with regular expression in Perl, Python, and other languages.
// for instance the regular expression for path '/articles/{id}' will be '^/articles/(?P<v0>[^/]+)$'
p, err := route.GetPathRegexp()
if err != nil {
return err
}
// qr will contain a list of regular expressions with the same semantics as GetPathRegexp,
// just applied to the Queries pairs instead, e.g., 'Queries("surname", "{surname}") will return
// {"^surname=(?P<v0>.*)$}. Where each combined query pair will have an entry in the list.
qr, err := route.GetQueriesRegexp()
if err != nil {
return err
}
m, err := route.GetMethods()
if err != nil {
return err
}
fmt.Println(strings.Join(m, ","), strings.Join(qt, ","), strings.Join(qr, ","), t, p)
return nil
})
http.Handle("/", r)
}
```
### Static Files
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:
request that matches "/static/\*". This makes it easy to serve static files with mux:
```go