1
0
Fork 0
mirror of https://github.com/Luzifer/staticmap.git synced 2024-10-18 07:34:23 +00:00

Remove old vendoring

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2021-02-19 19:21:48 +01:00
parent b480398d1c
commit bfc88cb04e
Signed by: luzifer
GPG key ID: 0066F03ED215AD7D
534 changed files with 0 additions and 229356 deletions

173
Gopkg.lock generated
View file

@ -1,173 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/Luzifer/go-staticmaps"
packages = ["."]
revision = "320790ed53294a789e715b3d0d5da8110efea1a2"
[[projects]]
name = "github.com/Luzifer/go_helpers"
packages = [
"accessLogger",
"http",
"str"
]
revision = "b0da2aa67ecc05ee4c8848d679b4a11a2fa578b2"
version = "v2.6.0"
[[projects]]
name = "github.com/Luzifer/rconfig"
packages = ["."]
revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/Wessie/appdirs"
packages = ["."]
revision = "6573e894f8e294cbae0c4e45c25ff9f2e2918a4e"
[[projects]]
name = "github.com/didip/tollbooth"
packages = [
".",
"errors",
"libstring",
"limiter"
]
revision = "c95eaa3ddc98f635a91e218b48727fb2e06613ea"
version = "v4.0.0"
[[projects]]
branch = "master"
name = "github.com/flopp/go-coordsparser"
packages = ["."]
revision = "845bca739e263e1cd38de25024a47b4d6acbfc1f"
[[projects]]
name = "github.com/fogleman/gg"
packages = ["."]
revision = "6166aa3c1afaee416f384645a81636267aee6d25"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/golang/freetype"
packages = [
"raster",
"truetype"
]
revision = "e2365dfdc4a05e4b8299a783240d4a7d5a65d4e4"
[[projects]]
branch = "master"
name = "github.com/golang/geo"
packages = [
"r1",
"r2",
"r3",
"s1",
"s2"
]
revision = "e41ca803f92c4c1770133cfa5b4fc8249a7dbe82"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
branch = "master"
name = "github.com/lucasb-eyer/go-colorful"
packages = ["."]
revision = "d9cec903b20cbeda6062366e460c2c1bdc717e4d"
[[projects]]
name = "github.com/patrickmn/go-cache"
packages = ["."]
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 = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
branch = "master"
name = "github.com/tkrajina/gpxgo"
packages = ["gpx"]
revision = "7848cf26f5a58b4a4e23b89a4b67cfc3d52dd042"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "027cca12c2d63e3d62b670d901e8a2c95854feec"
[[projects]]
branch = "master"
name = "golang.org/x/image"
packages = [
"font",
"font/basicfont",
"font/plan9font",
"math/fixed"
]
revision = "af66defab954cb421ca110193eed9477c8541e2a"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context"]
revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
revision = "6c888cc515d3ed83fc103cf1d84468aad274b0a7"
[[projects]]
branch = "master"
name = "golang.org/x/time"
packages = ["rate"]
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
[[projects]]
branch = "v2"
name = "gopkg.in/validator.v2"
packages = ["."]
revision = "135c24b11c19e52befcae2ec3fca5d9b78c4e98e"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "c59f72846eca4898dab7e7c3fc9a28b340f42aafbe4ac4866ad1ae33c382ea76"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -1,66 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/Luzifer/go-staticmaps"
[[constraint]]
name = "github.com/Luzifer/go_helpers"
version = "2.3.1"
[[constraint]]
name = "github.com/Luzifer/rconfig"
version = "1.2.0"
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.5"
[[constraint]]
name = "github.com/didip/tollbooth"
version = "4.0.0"
[[constraint]]
name = "github.com/fogleman/gg"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/golang/geo"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.1"
[[constraint]]
branch = "master"
name = "github.com/lucasb-eyer/go-colorful"
[prune]
go-tests = true
unused-packages = true

View file

@ -1 +0,0 @@
*.png

View file

@ -1,36 +0,0 @@
language: go
go:
- 1.9
- master
install:
- go get -t ./...
matrix:
allow_failures:
- go: master
# Don't wait for tip tests to finish. Mark the test run green if the
# tests pass on the stable versions of Go.
fast_finish: true
notifications:
email: false
# Anything in before_script that returns a nonzero exit code will
# flunk the build and immediately stop. It's sorta like having
# set -e enabled in bash.
before_script:
- GO_FILES=$(find . -iname '*.go' -type f) # All the .go files
- go get github.com/golang/lint/golint # Linter
- go get honnef.co/go/tools/cmd/megacheck # Badass static analyzer/linter
- go get github.com/fzipp/gocyclo
# script always run to completion (set +e). All of these code checks are must haves
# in a modern Go project.
script:
- test -z $(gofmt -s -l $GO_FILES) # Fail if a .go file hasn't been formatted with gofmt
- go vet ./... # go vet is the official Go static analyzer
- megacheck ./... # "go vet on steroids" + linter
- gocyclo -over 19 $GO_FILES # forbid code with huge functions
- golint -set_exit_status $(go list ./...) # one last linter

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Florian Pigorsch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,249 +0,0 @@
[![GoDoc](https://godoc.org/github.com/flopp/go-staticmaps?status.svg)](https://godoc.org/github.com/flopp/go-staticmaps)
[![Go Report Card](https://goreportcard.com/badge/github.com/flopp/go-staticmaps)](https://goreportcard.com/report/flopp/go-staticmaps)
[![Build Status](https://travis-ci.org/flopp/go-staticmaps.svg?branch=master)](https://travis-ci.org/flopp/go-staticmaps)
[![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/flopp/go-staticmaps/)
# go-staticmaps
A go (golang) library and command line tool to render static map images using OpenStreetMap tiles.
## What?
go-staticmaps is a golang library that allows you to create nice static map images from OpenStreetMap tiles, along with markers of different size and color, as well as paths and colored areas.
go-staticmaps comes with a command line tool called `create-static-map` for use in shell scripts, etc.
![Static map of the Berlin Marathon](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/berlin-marathon.png)
## How?
### Installation
Installing go-staticmaps is as easy as
```bash
go get -u github.com/flopp/go-staticmaps
```
For the command line tool, use
```bash
go get -u github.com/flopp/go-staticmaps/create-static-map
```
Of course, your local Go installation must be setup up properly.
### Library Usage
Create a 400x300 pixel map with a red marker:
```go
import (
"image/color"
"github.com/flopp/go-staticmaps"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
func main() {
ctx := sm.NewContext()
ctx.SetSize(400, 300)
ctx.AddMarker(sm.NewMarker(s2.LatLngFromDegrees(52.514536, 13.350151), color.RGBA{0xff, 0, 0, 0xff}, 16.0))
img, err := ctx.Render()
if err != nil {
panic(err)
}
if err := gg.SavePNG("my-map.png", img); err != nil {
panic(err)
}
}
```
See [GoDoc](https://godoc.org/github.com/flopp/go-staticmaps) for a complete documentation and the source code of the [command line tool](https://github.com/flopp/go-staticmaps/blob/master/create-static-map/create-static-map.go) for an example how to use the package.
### Command Line Usage
Usage:
create-static-map [OPTIONS]
Creates a static map
Application Options:
--width=PIXELS Width of the generated static map image (default: 512)
--height=PIXELS Height of the generated static map image (default: 512)
-o, --output=FILENAME Output file name (default: map.png)
-t, --type=MAPTYPE Select the map type; list possible map types with '--type list'
-c, --center=LATLNG Center coordinates (lat,lng) of the static map
-z, --zoom=ZOOMLEVEL Zoom factor
-b, --bbox=NW_LATLNG|SE_LATLNG
Set the bounding box (NW_LATLNG = north-western point of the
bounding box, SW_LATLNG = southe-western point of the bounding
box)
--background=COLOR Background color (default: transparent)
-u, --useragent=USERAGENT
Overwrite the default HTTP user agent string
-m, --marker=MARKER Add a marker to the static map
-p, --path=PATH Add a path to the static map
-a, --area=AREA Add an area to the static map
-C, --circle=CIRCLE Add a circle to the static map
Help Options:
-h, --help Show this help message
### General
The command line interface tries to resemble [Google's Static Maps API](https://developers.google.com/maps/documentation/static-maps/intro).
If neither `--bbox`, `--center`, nor `--zoom` are given, the map extent is determined from the specified markers, paths and areas.
`--background` lets you specify a color used for map areas that are not covered by map tiles (areas north of 85°/south of -85°).
### Markers
The `--marker` option defines one or more map markers of the same style. Use multiple `--marker` options to add markers of different styles.
--marker MARKER_STYLES|LATLNG|LATLNG|...
`LATLNG` is a comma separated pair of latitude and longitude, e.g. `52.5153,13.3564`.
`MARKER_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
- `size:SIZE` - where `SIZE` is one of `mid`, `small`, `tiny`, or some number > 0 (default: `mid`)
- `label:LABEL` - where `LABEL` is an alpha numeric character, i.e. `A`-`Z`, `a`-`z`, `0`-`9`; (default: no label)
- `labelcolor:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `black` or `white`, depending on the marker color)
### Paths
The `--path` option defines a path on the map. Use multiple `--path` options to add multiple paths to the map.
--path PATH_STYLES|LATLNG|LATLNG|...
or
--path PATH_STYLES|gpx:my_gpx_file.gpx
`PATH_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
- `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`)
### Areas
The `--area` option defines a closed area on the map. Use multiple `--area` options to add multiple areas to the map.
--area AREA_STYLES|LATLNG|LATLNG|...
`AREA_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
- `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`)
- `fill:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: none)
### Circles
The `--circles` option defines one or more circles of the same style. Use multiple `--circle` options to add circles of different styles.
--circle CIRCLE_STYLES|LATLNG|LATLNG|...
`LATLNG` is a comma separated pair of latitude and longitude, e.g. `52.5153,13.3564`.
`CIRCLE_STYLES` consists of a set of style descriptors separated by the pipe character `|`:
- `color:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: `red`)
- `fill:COLOR` - where `COLOR` is either of the form `0xRRGGBB`, `0xRRGGBBAA`, or one of `black`, `blue`, `brown`, `green`, `orange`, `purple`, `red`, `yellow`, `white` (default: no fill color)
- `radius:RADIUS` - where `RADIUS` is te circle radius in meters (default: `100.0`)
- `weight:WEIGHT` - where `WEIGHT` is the line width in pixels (defaut: `5`)
## Examples
### Basic Maps
Centered at "N 52.514536 E 13.350151" with zoom level 10:
```bash
$ create-static-map --width 600 --height 400 -o map1.png -c "52.514536,13.350151" -z 10
```
![Example 1](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/map1.png)
A map with a marker at "N 52.514536 E 13.350151" with zoom level 14 (no need to specify the map's center - it is automatically computed from the marker(s)):
```bash
$ create-static-map --width 600 --height 400 -o map2.png -z 14 -m "52.514536,13.350151"
```
![Example 2](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/map2.png)
A map with two markers (red and green). If there are more than two markers in the map, a *good* zoom level can be determined automatically:
```bash
$ create-static-map --width 600 --height 400 -o map3.png -m "color:red|52.514536,13.350151" -m "color:green|52.516285,13.377746"
```
![Example 3](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/map3.png)
### Create a map of the Berlin Marathon
create-static-map --width 800 --height 600 \
--marker "color:green|52.5153,13.3564" \
--marker "color:red|52.5160,13.3711" \
--output "berlin-marathon.png" \
--path "color:blue|weight:2|gpx:berlin-marathon.gpx"
![Static map of the Berlin Marathon](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/berlin-marathon.png)
### Create a map of the US capitals
create-static-map --width 800 --height 400 \
--output "us-capitals.png" \
--marker "color:blue|size:tiny|32.3754,-86.2996|58.3637,-134.5721|33.4483,-112.0738|34.7244,-92.2789|\
38.5737,-121.4871|39.7551,-104.9881|41.7665,-72.6732|39.1615,-75.5136|30.4382,-84.2806|33.7545,-84.3897|\
21.2920,-157.8219|43.6021,-116.2125|39.8018,-89.6533|39.7670,-86.1563|41.5888,-93.6203|39.0474,-95.6815|\
38.1894,-84.8715|30.4493,-91.1882|44.3294,-69.7323|38.9693,-76.5197|42.3589,-71.0568|42.7336,-84.5466|\
44.9446,-93.1027|32.3122,-90.1780|38.5698,-92.1941|46.5911,-112.0205|40.8136,-96.7026|39.1501,-119.7519|\
43.2314,-71.5597|40.2202,-74.7642|35.6816,-105.9381|42.6517,-73.7551|35.7797,-78.6434|46.8084,-100.7694|\
39.9622,-83.0007|35.4931,-97.4591|44.9370,-123.0272|40.2740,-76.8849|41.8270,-71.4087|34.0007,-81.0353|\
44.3776,-100.3177|36.1589,-86.7821|30.2687,-97.7452|40.7716,-111.8882|44.2627,-72.5716|37.5408,-77.4339|\
47.0449,-122.9016|38.3533,-81.6354|43.0632,-89.4007|41.1389,-104.8165"
![Static map of the US capitals](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/us-capitals.png)
### Create a map of Australia
...where the Northern Territory is highlighted and the capital Canberra is marked.
create-static-map --width 800 --height 600 \
--center="-26.284973,134.303764" \
--output "australia.png" \
--marker "color:blue|-35.305200,149.121574" \
--area "color:0x00FF00|fill:0x00FF007F|weight:2|-25.994024,129.013847|-25.994024,137.989677|-16.537670,138.011649|\
-14.834820,135.385917|-12.293236,137.033866|-11.174554,130.398124|-12.925791,130.167411|-14.866678,129.002860"
![Static map of Australia](https://raw.githubusercontent.com/flopp/flopp.github.io/master/go-staticmaps/australia.png)
## Acknowledgements
Besides the go standard library, go-staticmaps uses
- [OpenStreetMap](http://openstreetmap.org/), [Thunderforest](http://www.thunderforest.com/), [OpenTopoMap](http://www.opentopomap.org/), [Stamen](http://maps.stamen.com/) and [Carto](http://carto.com) as map tile providers
- [Go Graphics](https://github.com/fogleman/gg) for 2D drawing
- [S2 geometry library](https://github.com/golang/geo) for spherical geometry calculations
- [appdirs](https://github.com/Wessie/appdirs) for platform specific system directories
- [gpxgo](github.com/tkrajina/gpxgo) for loading GPX files
- [go-coordsparser](https://github.com/flopp/go-coordsparser) for parsing geo coordinates
## Contributors
- [Kooper](https://github.com/Kooper): fixed *library usage examples*
- [felix](https://github.com/felix): added *more tile servers*
- [wiless](https://github.com/wiless): suggested to add user definable *marker label colors*
- [noki](https://github.com/Noki): suggested to add a user definable *bounding box*
- [digitocero](https://github.com/digitocero): reported and fixed *type mismatch error*
- [bcicen](https://github.com/bcicen): reported and fixed *syntax error in examples*
- [pshevtsov](https://github.com/pshevtsov): fixed *drawing of empty attribution strings*
- [Luzifer](https://github.com/Luzifer): added *overwritable user agent strings* to comply with the OSM tile usage policy
- [Jason Fox](https://github.com/jasonpfox): added `RenderWithBounds` function
- [Alexander A. Kapralov](https://github.com/alnkapa): initial *circles* implementation
- [tsukumaru](https://github.com/tsukumaru): added `NewArea` and `NewPath` functions
## License
Copyright 2016, 2017 Florian Pigorsch & Contributors. All rights reserved.
Use of this source code is governed by a MIT-style license that can be found in the LICENSE file.

View file

@ -1,104 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"image/color"
"strconv"
"strings"
"github.com/flopp/go-coordsparser"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
// Area represents a area or area on the map
type Area struct {
MapObject
Positions []s2.LatLng
Color color.Color
Fill color.Color
Weight float64
}
// NewArea creates a new Area
func NewArea(positions []s2.LatLng, col color.Color, fill color.Color, weight float64) *Area {
a := new(Area)
a.Positions = positions
a.Color = col
a.Fill = fill
a.Weight = weight
return a
}
// ParseAreaString parses a string and returns an area
func ParseAreaString(s string) (*Area, error) {
area := new(Area)
area.Color = color.RGBA{0xff, 0, 0, 0xff}
area.Fill = color.Transparent
area.Weight = 5.0
for _, ss := range strings.Split(s, "|") {
if ok, suffix := hasPrefix(ss, "color:"); ok {
var err error
area.Color, err = ParseColorString(suffix)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "fill:"); ok {
var err error
area.Fill, err = ParseColorString(suffix)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "weight:"); ok {
var err error
area.Weight, err = strconv.ParseFloat(suffix, 64)
if err != nil {
return nil, err
}
} else {
lat, lng, err := coordsparser.Parse(ss)
if err != nil {
return nil, err
}
area.Positions = append(area.Positions, s2.LatLngFromDegrees(lat, lng))
}
}
return area, nil
}
func (p *Area) extraMarginPixels() float64 {
return 0.5 * p.Weight
}
func (p *Area) bounds() s2.Rect {
r := s2.EmptyRect()
for _, ll := range p.Positions {
r = r.AddPoint(ll)
}
return r
}
func (p *Area) draw(gc *gg.Context, trans *transformer) {
if len(p.Positions) <= 1 {
return
}
gc.ClearPath()
gc.SetLineWidth(p.Weight)
gc.SetLineCap(gg.LineCapRound)
gc.SetLineJoin(gg.LineJoinRound)
for _, ll := range p.Positions {
gc.LineTo(trans.ll2p(ll))
}
gc.ClosePath()
gc.SetColor(p.Fill)
gc.FillPreserve()
gc.SetColor(p.Color)
gc.Stroke()
}

View file

@ -1,53 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"fmt"
"math"
"github.com/golang/geo/s1"
"github.com/golang/geo/s2"
)
// CreateBBox creates a bounding box from a north-western point
// (lat/lng in degrees) and a south-eastern point (lat/lng in degrees).
// Note that you can create a bounding box wrapping over the antimeridian at
// lng=+-/180° by nwlng > selng.
func CreateBBox(nwlat float64, nwlng float64, selat float64, selng float64) (*s2.Rect, error) {
if nwlat < -90 || nwlat > 90 {
return nil, fmt.Errorf("Out of range nwlat (%f) must be in [-90, 90]", nwlat)
}
if nwlng < -180 || nwlng > 180 {
return nil, fmt.Errorf("Out of range nwlng (%f) must be in [-180, 180]", nwlng)
}
if selat < -90 || selat > 90 {
return nil, fmt.Errorf("Out of range selat (%f) must be in [-90, 90]", selat)
}
if selng < -180 || selng > 180 {
return nil, fmt.Errorf("Out of range selng (%f) must be in [-180, 180]", selng)
}
if nwlat == selat {
return nil, fmt.Errorf("nwlat and selat must not be equal")
}
if nwlng == selng {
return nil, fmt.Errorf("nwlng and selng must not be equal")
}
bbox := new(s2.Rect)
if selat < nwlat {
bbox.Lat.Lo = selat * math.Pi / 180.0
bbox.Lat.Hi = nwlat * math.Pi / 180.0
} else {
bbox.Lat.Lo = nwlat * math.Pi / 180.0
bbox.Lat.Hi = selat * math.Pi / 180.0
}
bbox.Lng = s1.IntervalFromEndpoints(nwlng*math.Pi/180.0, selng*math.Pi/180.0)
return bbox, nil
}

View file

@ -1,128 +0,0 @@
package sm
import (
"image/color"
"log"
"math"
"strings"
"strconv"
"github.com/flopp/go-coordsparser"
"github.com/fogleman/gg"
"github.com/golang/geo/s1"
"github.com/golang/geo/s2"
)
// Circle represents a circle on the map
type Circle struct {
MapObject
Position s2.LatLng
Color color.Color
Fill color.Color
Weight float64
Radius float64 // in m.
}
// NewCircle creates a new circle
func NewCircle(pos s2.LatLng, col, fill color.Color, radius, weight float64) *Circle {
return &Circle{
Position: pos,
Color: col,
Fill: fill,
Weight: weight,
Radius: radius,
}
}
// ParseCircleString parses a string and returns an array of circles
func ParseCircleString(s string) (circles []*Circle, err error) {
circles = make([]*Circle, 0, 0)
var col color.Color = color.RGBA{0xff, 0, 0, 0xff}
var fill color.Color = color.Transparent
radius := 100.0
weight := 5.0
for _, ss := range strings.Split(s, "|") {
if ok, suffix := hasPrefix(ss, "color:"); ok {
col, err = ParseColorString(suffix)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "fill:"); ok {
fill, err = ParseColorString(suffix)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "radius:"); ok {
if radius, err = strconv.ParseFloat(suffix, 64); err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "weight:"); ok {
if weight, err = strconv.ParseFloat(suffix, 64); err != nil {
return nil, err
}
} else {
lat, lng, err := coordsparser.Parse(ss)
if err != nil {
return nil, err
}
c := NewCircle(s2.LatLngFromDegrees(lat, lng), col, fill, radius, weight)
circles = append(circles, c)
}
}
return circles, nil
}
func (m *Circle) getLatLng(plus bool) s2.LatLng {
const (
R = 6371000.0
)
th := m.Radius / R
br := 0 / float64(s1.Degree)
if !plus {
th *= -1
}
lat := m.Position.Lat.Radians()
lat1 := math.Asin(math.Sin(lat)*math.Cos(th) + math.Cos(lat)*math.Sin(th)*math.Cos(br))
lng1 := m.Position.Lng.Radians() +
math.Atan2(math.Sin(br)*math.Sin(th)*math.Cos(lat),
math.Cos(th)-math.Sin(lat)*math.Sin(lat1))
return s2.LatLng{
Lat: s1.Angle(lat1),
Lng: s1.Angle(lng1),
}
}
func (m *Circle) extraMarginPixels() float64 {
return 0.5 * m.Weight
}
func (m *Circle) bounds() s2.Rect {
r := s2.EmptyRect()
r = r.AddPoint(m.getLatLng(false))
r = r.AddPoint(m.getLatLng(true))
return r
}
func (m *Circle) draw(gc *gg.Context, trans *transformer) {
if !CanDisplay(m.Position) {
log.Printf("Circle coordinates not displayable: %f/%f", m.Position.Lat.Degrees(), m.Position.Lng.Degrees())
return
}
ll := m.getLatLng(true)
x, y := trans.ll2p(m.Position)
x1, y1 := trans.ll2p(ll)
radius := math.Sqrt(math.Pow(x1-x, 2) + math.Pow(y1-y, 2))
gc.ClearPath()
gc.SetLineWidth(m.Weight)
gc.SetLineCap(gg.LineCapRound)
gc.SetLineJoin(gg.LineJoinRound)
gc.DrawCircle(x, y, radius)
gc.SetColor(m.Fill)
gc.FillPreserve()
gc.SetColor(m.Color)
gc.Stroke()
}

View file

@ -1,67 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"fmt"
"image/color"
"regexp"
"strings"
)
// ParseColorString parses hex color strings (i.e. `0xRRGGBB`, `#RRGGBB`, `0xRRGGBBAA`, `#RRGGBBAA`), and names colors (e.g. 'black', 'blue', ...)
func ParseColorString(s string) (color.Color, error) {
s = strings.ToLower(strings.TrimSpace(s))
re := regexp.MustCompile(`^(0x|#)([A-Fa-f0-9]{6})$`)
matches := re.FindStringSubmatch(s)
if matches != nil {
var r, g, b int
fmt.Sscanf(matches[2], "%2x%2x%2x", &r, &g, &b)
return color.RGBA{uint8(r), uint8(g), uint8(b), 0xff}, nil
}
re = regexp.MustCompile(`^(0x|#)([A-Fa-f0-9]{8})$`)
matches = re.FindStringSubmatch(s)
if matches != nil {
var r, g, b, a int
fmt.Sscanf(matches[2], "%2x%2x%2x%2x", &r, &g, &b, &a)
rr := float64(r) * float64(a) / 256.0
gg := float64(g) * float64(a) / 256.0
bb := float64(b) * float64(a) / 256.0
return color.RGBA{uint8(rr), uint8(gg), uint8(bb), uint8(a)}, nil
}
switch s {
case "black":
return color.RGBA{0x00, 0x00, 0x00, 0xff}, nil
case "blue":
return color.RGBA{0x00, 0x00, 0xff, 0xff}, nil
case "brown":
return color.RGBA{0x96, 0x4b, 0x00, 0xff}, nil
case "green":
return color.RGBA{0x00, 0xff, 0x00, 0xff}, nil
case "orange":
return color.RGBA{0xff, 0x7f, 0x00, 0xff}, nil
case "purple":
return color.RGBA{0x7f, 0x00, 0x7f, 0xff}, nil
case "red":
return color.RGBA{0xff, 0x00, 0x00, 0xff}, nil
case "yellow":
return color.RGBA{0xff, 0xff, 0x00, 0xff}, nil
case "white":
return color.RGBA{0xff, 0xff, 0xff, 0xff}, nil
case "transparent":
return color.RGBA{0x00, 0x00, 0x00, 0x00}, nil
}
return color.Transparent, fmt.Errorf("Cannot parse color string: %s", s)
}
// Luminance computes the luminance (~ brightness) of the given color. Range: 0.0 for black to 1.0 for white.
func Luminance(col color.Color) float64 {
r, g, b, _ := col.RGBA()
return (float64(r)*0.299 + float64(g)*0.587 + float64(b)*0.114) / float64(0xffff)
}

View file

@ -1,503 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package sm (~ static maps) renders static map images from OSM tiles with markers, paths, and filled areas.
package sm
import (
"errors"
"image"
"image/color"
"image/draw"
"log"
"math"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
// Context holds all information about the map image that is to be rendered
type Context struct {
width int
height int
hasZoom bool
zoom int
hasCenter bool
center s2.LatLng
hasBoundingBox bool
boundingBox s2.Rect
background color.Color
markers []*Marker
paths []*Path
areas []*Area
circles []*Circle
overlays []*TileProvider
userAgent string
tileProvider *TileProvider
overrideAttribution *string
}
// NewContext creates a new instance of Context
func NewContext() *Context {
t := new(Context)
t.width = 512
t.height = 512
t.hasZoom = false
t.hasCenter = false
t.hasBoundingBox = false
t.background = nil
t.userAgent = ""
t.tileProvider = NewTileProviderOpenStreetMaps()
return t
}
// SetTileProvider sets the TileProvider to be used
func (m *Context) SetTileProvider(t *TileProvider) {
m.tileProvider = t
}
// SetUserAgent sets the HTTP user agent string used when downloading map tiles
func (m *Context) SetUserAgent(a string) {
m.userAgent = a
}
// SetSize sets the size of the generated image
func (m *Context) SetSize(width, height int) {
m.width = width
m.height = height
}
// SetZoom sets the zoom level
func (m *Context) SetZoom(zoom int) {
m.zoom = zoom
m.hasZoom = true
}
// SetCenter sets the center coordinates
func (m *Context) SetCenter(center s2.LatLng) {
m.center = center
m.hasCenter = true
}
// SetBoundingBox sets the bounding box
func (m *Context) SetBoundingBox(bbox s2.Rect) {
m.boundingBox = bbox
m.hasBoundingBox = true
}
// SetBackground sets the background color (used as a fallback for areas without map tiles)
func (m *Context) SetBackground(col color.Color) {
m.background = col
}
// AddMarker adds a marker to the Context
func (m *Context) AddMarker(marker *Marker) {
m.markers = append(m.markers, marker)
}
// ClearMarkers removes all markers from the Context
func (m *Context) ClearMarkers() {
m.markers = nil
}
// AddPath adds a path to the Context
func (m *Context) AddPath(path *Path) {
m.paths = append(m.paths, path)
}
// ClearPaths removes all paths from the Context
func (m *Context) ClearPaths() {
m.paths = nil
}
// AddArea adds an area to the Context
func (m *Context) AddArea(area *Area) {
m.areas = append(m.areas, area)
}
// ClearAreas removes all areas from the Context
func (m *Context) ClearAreas() {
m.areas = nil
}
// AddCircle adds an circle to the Context
func (m *Context) AddCircle(circle *Circle) {
m.circles = append(m.circles, circle)
}
// ClearCircles removes all circles from the Context
func (m *Context) ClearCircles() {
m.circles = nil
}
// AddOverlay adds an overlay to the Context
func (m *Context) AddOverlay(overlay *TileProvider) {
m.overlays = append(m.overlays, overlay)
}
// ClearOverlays removes all overlays from the Context
func (m *Context) ClearOverlays() {
m.overlays = nil
}
// 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) OverrideAttribution(attribution string) {
m.overrideAttribution = &attribution
}
func (m *Context) determineBounds() s2.Rect {
r := s2.EmptyRect()
for _, marker := range m.markers {
r = r.Union(marker.bounds())
}
for _, path := range m.paths {
r = r.Union(path.bounds())
}
for _, area := range m.areas {
r = r.Union(area.bounds())
}
for _, circle := range m.circles {
r = r.Union(circle.bounds())
}
return r
}
func (m *Context) determineExtraMarginPixels() float64 {
p := 0.0
for _, marker := range m.markers {
if pp := marker.extraMarginPixels(); pp > p {
p = pp
}
}
for _, path := range m.paths {
if pp := path.extraMarginPixels(); pp > p {
p = pp
}
}
for _, area := range m.areas {
if pp := area.extraMarginPixels(); pp > p {
p = pp
}
}
for _, circle := range m.circles {
if pp := circle.extraMarginPixels(); pp > p {
p = pp
}
}
return p
}
func (m *Context) determineZoom(bounds s2.Rect, center s2.LatLng) int {
b := bounds.AddPoint(center)
if b.IsEmpty() || b.IsPoint() {
return 15
}
tileSize := m.tileProvider.TileSize
margin := 4.0 + m.determineExtraMarginPixels()
w := (float64(m.width) - 2.0*margin) / float64(tileSize)
h := (float64(m.height) - 2.0*margin) / float64(tileSize)
minX := (b.Lo().Lng.Degrees() + 180.0) / 360.0
maxX := (b.Hi().Lng.Degrees() + 180.0) / 360.0
minY := (1.0 - math.Log(math.Tan(b.Lo().Lat.Radians())+(1.0/math.Cos(b.Lo().Lat.Radians())))/math.Pi) / 2.0
maxY := (1.0 - math.Log(math.Tan(b.Hi().Lat.Radians())+(1.0/math.Cos(b.Hi().Lat.Radians())))/math.Pi) / 2.0
dx := maxX - minX
for dx < 0 {
dx = dx + 1
}
for dx > 1 {
dx = dx - 1
}
dy := math.Abs(maxY - minY)
zoom := 1
for zoom < 30 {
tiles := float64(uint(1) << uint(zoom))
if dx*tiles > w || dy*tiles > h {
return zoom - 1
}
zoom = zoom + 1
}
return 15
}
func (m *Context) determineZoomCenter() (int, s2.LatLng, error) {
bounds := m.determineBounds()
if m.hasBoundingBox && !m.boundingBox.IsEmpty() {
center := m.boundingBox.Center()
return m.determineZoom(m.boundingBox, center), center, nil
} else if m.hasCenter {
if m.hasZoom {
return m.zoom, m.center, nil
}
return m.determineZoom(bounds, m.center), m.center, nil
} else if !bounds.IsEmpty() {
center := bounds.Center()
if m.hasZoom {
return m.zoom, center, nil
}
return m.determineZoom(bounds, center), center, nil
}
return 0, s2.LatLngFromDegrees(0, 0), errors.New("Cannot determine map extent: no center coordinates given, no bounding box given, no content (markers, paths, areas) given")
}
type transformer struct {
zoom int
numTiles float64 // number of tiles per dimension at this zoom level
tileSize int // tile size in pixels from this provider
pWidth, pHeight int // pixel size of returned set of tiles
pCenterX, pCenterY int // pixel location of requested center in set of tiles
tCountX, tCountY int // download area in tile units
tCenterX, tCenterY float64 // tile index to requested center
tOriginX, tOriginY int // bottom left tile to download
pMinX, pMaxX int
}
func newTransformer(width int, height int, zoom int, llCenter s2.LatLng, tileSize int) *transformer {
t := new(transformer)
t.zoom = zoom
t.numTiles = math.Exp2(float64(t.zoom))
t.tileSize = tileSize
// fractional tile index to center of requested area
t.tCenterX, t.tCenterY = t.ll2t(llCenter)
ww := float64(width) / float64(tileSize)
hh := float64(height) / float64(tileSize)
// origin tile to fulfill request
t.tOriginX = int(math.Floor(t.tCenterX - 0.5*ww))
t.tOriginY = int(math.Floor(t.tCenterY - 0.5*hh))
// tiles in each axis to fulfill request
t.tCountX = 1 + int(math.Floor(t.tCenterX+0.5*ww)) - t.tOriginX
t.tCountY = 1 + int(math.Floor(t.tCenterY+0.5*hh)) - t.tOriginY
// final pixel dimensions of area returned
t.pWidth = t.tCountX * tileSize
t.pHeight = t.tCountY * tileSize
// Pixel location in returned image for center of requested area
t.pCenterX = int((t.tCenterX - float64(t.tOriginX)) * float64(tileSize))
t.pCenterY = int((t.tCenterY - float64(t.tOriginY)) * float64(tileSize))
t.pMinX = t.pCenterX - width/2
t.pMaxX = t.pMinX + width
return t
}
// ll2t returns fractional tile index for a lat/lng points
func (t *transformer) ll2t(ll s2.LatLng) (float64, float64) {
x := t.numTiles * (ll.Lng.Degrees() + 180.0) / 360.0
y := t.numTiles * (1 - math.Log(math.Tan(ll.Lat.Radians())+(1.0/math.Cos(ll.Lat.Radians())))/math.Pi) / 2.0
return x, y
}
func (t *transformer) ll2p(ll s2.LatLng) (float64, float64) {
x, y := t.ll2t(ll)
x = float64(t.pCenterX) + (x-t.tCenterX)*float64(t.tileSize)
y = float64(t.pCenterY) + (y-t.tCenterY)*float64(t.tileSize)
offset := t.numTiles * float64(t.tileSize)
if x < float64(t.pMinX) {
for x < float64(t.pMinX) {
x = x + offset
}
} else if x >= float64(t.pMaxX) {
for x >= float64(t.pMaxX) {
x = x - offset
}
}
return x, y
}
// Rect returns an s2.Rect bounding box around the set of tiles described by transformer
func (t *transformer) Rect() (bbox s2.Rect) {
// transform from https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Go
invNumTiles := 1.0 / t.numTiles
// Get latitude bounds
n := math.Pi - 2.0*math.Pi*float64(t.tOriginY)*invNumTiles
bbox.Lat.Hi = math.Atan(0.5 * (math.Exp(n) - math.Exp(-n)))
n = math.Pi - 2.0*math.Pi*float64(t.tOriginY+t.tCountY)*invNumTiles
bbox.Lat.Lo = math.Atan(0.5 * (math.Exp(n) - math.Exp(-n)))
// Get longtitude bounds, much easier
bbox.Lng.Lo = float64(t.tOriginX)*invNumTiles*2.0*math.Pi - math.Pi
bbox.Lng.Hi = float64(t.tOriginX+t.tCountX)*invNumTiles*2.0*math.Pi - math.Pi
return bbox
}
// Render actually renders the map image including all map objects (markers, paths, areas)
func (m *Context) Render() (image.Image, error) {
zoom, center, err := m.determineZoomCenter()
if err != nil {
return nil, err
}
tileSize := m.tileProvider.TileSize
trans := newTransformer(m.width, m.height, zoom, center, tileSize)
img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight))
gc := gg.NewContextForRGBA(img)
if m.background != nil {
draw.Draw(img, img.Bounds(), &image.Uniform{m.background}, image.ZP, draw.Src)
}
// fetch and draw tiles to img
layers := []*TileProvider{m.tileProvider}
if m.overlays != nil {
layers = append(layers, m.overlays...)
}
for _, layer := range layers {
if err := m.renderLayer(gc, zoom, trans, tileSize, layer); err != nil {
return nil, err
}
}
// draw map objects
for _, area := range m.areas {
area.draw(gc, trans)
}
for _, path := range m.paths {
path.draw(gc, trans)
}
for _, marker := range m.markers {
marker.draw(gc, trans)
}
for _, circle := range m.circles {
circle.draw(gc, trans)
}
// crop image
croppedImg := image.NewRGBA(image.Rect(0, 0, int(m.width), int(m.height)))
draw.Draw(croppedImg, image.Rect(0, 0, int(m.width), int(m.height)),
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 attribution == "" {
return croppedImg, nil
}
_, 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(attribution, 4.0, float64(m.height)-4.0)
return croppedImg, nil
}
// RenderWithBounds actually renders the map image including all map objects (markers, paths, areas).
// The returned image covers requested area as well as any tiles necessary to cover that area, which may
// be larger than the request.
//
// Specific bounding box of returned image is provided to support image registration with other data
func (m *Context) RenderWithBounds() (image.Image, s2.Rect, error) {
zoom, center, err := m.determineZoomCenter()
if err != nil {
return nil, s2.Rect{}, err
}
tileSize := m.tileProvider.TileSize
trans := newTransformer(m.width, m.height, zoom, center, tileSize)
img := image.NewRGBA(image.Rect(0, 0, trans.pWidth, trans.pHeight))
gc := gg.NewContextForRGBA(img)
if m.background != nil {
draw.Draw(img, img.Bounds(), &image.Uniform{m.background}, image.ZP, draw.Src)
}
// fetch and draw tiles to img
layers := []*TileProvider{m.tileProvider}
if m.overlays != nil {
layers = append(layers, m.overlays...)
}
for _, layer := range layers {
if err := m.renderLayer(gc, zoom, trans, tileSize, layer); err != nil {
return nil, s2.Rect{}, err
}
}
// draw map objects
for _, area := range m.areas {
area.draw(gc, trans)
}
for _, path := range m.paths {
path.draw(gc, trans)
}
for _, circle := range m.circles {
circle.draw(gc, trans)
}
for _, marker := range m.markers {
marker.draw(gc, trans)
}
// draw attribution
if m.tileProvider.Attribution == "" {
return img, trans.Rect(), nil
}
_, textHeight := gc.MeasureString(m.tileProvider.Attribution)
boxHeight := textHeight + 4.0
gc.SetRGBA(0.0, 0.0, 0.0, 0.5)
gc.DrawRectangle(0.0, float64(trans.pHeight)-boxHeight, float64(trans.pWidth), 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)
return img, trans.Rect(), nil
}
func (m *Context) renderLayer(gc *gg.Context, zoom int, trans *transformer, tileSize int, provider *TileProvider) error {
t := NewTileFetcher(provider)
if m.userAgent != "" {
t.SetUserAgent(m.userAgent)
}
tiles := (1 << uint(zoom))
for xx := 0; xx < trans.tCountX; xx++ {
x := trans.tOriginX + xx
if x < 0 {
x = x + tiles
} else if x >= tiles {
x = x - tiles
}
for yy := 0; yy < trans.tCountY; yy++ {
y := trans.tOriginY + yy
if y < 0 || y >= tiles {
log.Printf("Skipping out of bounds tile %d/%d", x, y)
} else {
if tileImg, err := t.Fetch(zoom, x, y); err == nil {
gc.DrawImage(tileImg, xx*tileSize, yy*tileSize)
} else {
log.Printf("Error downloading tile file: %s", err)
}
}
}
}
return nil
}

View file

@ -1,25 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
// MapObject is the interface for all objects on the map
type MapObject interface {
bounds() s2.Rect
extraMarginPixels() float64
draw(dc *gg.Context, trans *transformer)
}
// CanDisplay checks if pos is generally displayable (i.e. its latitude is in [-85,85])
func CanDisplay(pos s2.LatLng) bool {
const minLatitude float64 = -85.0
const maxLatitude float64 = 85.0
return (minLatitude <= pos.Lat.Degrees()) && (pos.Lat.Degrees() <= maxLatitude)
}

View file

@ -1,149 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"fmt"
"image/color"
"log"
"math"
"strconv"
"strings"
"github.com/flopp/go-coordsparser"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
)
// Marker represents a marker on the map
type Marker struct {
MapObject
Position s2.LatLng
Color color.Color
Size float64
Label string
LabelColor color.Color
}
// NewMarker creates a new Marker
func NewMarker(pos s2.LatLng, col color.Color, size float64) *Marker {
m := new(Marker)
m.Position = pos
m.Color = col
m.Size = size
m.Label = ""
if Luminance(m.Color) >= 0.5 {
m.LabelColor = color.RGBA{0x00, 0x00, 0x00, 0xff}
} else {
m.LabelColor = color.RGBA{0xff, 0xff, 0xff, 0xff}
}
return m
}
func parseSizeString(s string) (float64, error) {
switch {
case s == "mid":
return 16.0, nil
case s == "small":
return 12.0, nil
case s == "tiny":
return 8.0, nil
}
if ss, err := strconv.ParseFloat(s, 64); err != nil && ss > 0 {
return ss, nil
}
return 0.0, fmt.Errorf("Cannot parse size string: %s", s)
}
// ParseMarkerString parses a string and returns an array of markers
func ParseMarkerString(s string) ([]*Marker, error) {
markers := make([]*Marker, 0, 0)
var markerColor color.Color = color.RGBA{0xff, 0, 0, 0xff}
size := 16.0
label := ""
var labelColor color.Color
for _, ss := range strings.Split(s, "|") {
if ok, suffix := hasPrefix(ss, "color:"); ok {
var err error
markerColor, err = ParseColorString(suffix)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "label:"); ok {
label = suffix
} else if ok, suffix := hasPrefix(ss, "size:"); ok {
var err error
size, err = parseSizeString(suffix)
if err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "labelcolor:"); ok {
var err error
labelColor, err = ParseColorString(suffix)
if err != nil {
return nil, err
}
} else {
lat, lng, err := coordsparser.Parse(ss)
if err != nil {
return nil, err
}
m := NewMarker(s2.LatLngFromDegrees(lat, lng), markerColor, size)
m.Label = label
if labelColor != nil {
m.SetLabelColor(labelColor)
}
markers = append(markers, m)
}
}
return markers, nil
}
// SetLabelColor sets the color of the marker's text label
func (m *Marker) SetLabelColor(col color.Color) {
m.LabelColor = col
}
func (m *Marker) extraMarginPixels() float64 {
return 1.0 + 1.5*m.Size
}
func (m *Marker) bounds() s2.Rect {
r := s2.EmptyRect()
r = r.AddPoint(m.Position)
return r
}
func (m *Marker) draw(gc *gg.Context, trans *transformer) {
if !CanDisplay(m.Position) {
log.Printf("Marker coordinates not displayable: %f/%f", m.Position.Lat.Degrees(), m.Position.Lng.Degrees())
return
}
gc.ClearPath()
gc.SetLineJoin(gg.LineJoinRound)
gc.SetLineWidth(1.0)
radius := 0.5 * m.Size
x, y := trans.ll2p(m.Position)
gc.DrawArc(x, y-m.Size, radius, (90.0+60.0)*math.Pi/180.0, (360.0+90.0-60.0)*math.Pi/180.0)
gc.LineTo(x, y)
gc.ClosePath()
gc.SetColor(m.Color)
gc.FillPreserve()
gc.SetRGB(0, 0, 0)
gc.Stroke()
if m.Label != "" {
gc.SetColor(m.LabelColor)
gc.DrawStringAnchored(m.Label, x, y-m.Size, 0.5, 0.5)
}
}

View file

@ -1,113 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"image/color"
"strconv"
"strings"
"github.com/flopp/go-coordsparser"
"github.com/fogleman/gg"
"github.com/golang/geo/s2"
"github.com/tkrajina/gpxgo/gpx"
)
// Path represents a path or area on the map
type Path struct {
MapObject
Positions []s2.LatLng
Color color.Color
Weight float64
}
// NewPath creates a new Path
func NewPath(positions []s2.LatLng, col color.Color, weight float64) *Path {
p := new(Path)
p.Positions = positions
p.Color = col
p.Weight = weight
return p
}
// ParsePathString parses a string and returns a path
func ParsePathString(s string) ([]*Path, error) {
paths := make([]*Path, 0, 0)
currentPath := new(Path)
currentPath.Color = color.RGBA{0xff, 0, 0, 0xff}
currentPath.Weight = 5.0
for _, ss := range strings.Split(s, "|") {
if ok, suffix := hasPrefix(ss, "color:"); ok {
var err error
if currentPath.Color, err = ParseColorString(suffix); err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "weight:"); ok {
var err error
if currentPath.Weight, err = strconv.ParseFloat(suffix, 64); err != nil {
return nil, err
}
} else if ok, suffix := hasPrefix(ss, "gpx:"); ok {
gpxData, err := gpx.ParseFile(suffix)
if err != nil {
return nil, err
}
for _, trk := range gpxData.Tracks {
for _, seg := range trk.Segments {
p := new(Path)
p.Color = currentPath.Color
p.Weight = currentPath.Weight
for _, pt := range seg.Points {
p.Positions = append(p.Positions, s2.LatLngFromDegrees(pt.GetLatitude(), pt.GetLongitude()))
}
if len(p.Positions) > 0 {
paths = append(paths, p)
}
}
}
} else {
lat, lng, err := coordsparser.Parse(ss)
if err != nil {
return nil, err
}
currentPath.Positions = append(currentPath.Positions, s2.LatLngFromDegrees(lat, lng))
}
}
if len(currentPath.Positions) > 0 {
paths = append(paths, currentPath)
}
return paths, nil
}
func (p *Path) extraMarginPixels() float64 {
return 0.5 * p.Weight
}
func (p *Path) bounds() s2.Rect {
r := s2.EmptyRect()
for _, ll := range p.Positions {
r = r.AddPoint(ll)
}
return r
}
func (p *Path) draw(gc *gg.Context, trans *transformer) {
if len(p.Positions) <= 1 {
return
}
gc.ClearPath()
gc.SetLineWidth(p.Weight)
gc.SetLineCap(gg.LineCapRound)
gc.SetLineJoin(gg.LineJoinRound)
for _, ll := range p.Positions {
gc.LineTo(trans.ll2p(ll))
}
gc.SetColor(p.Color)
gc.Stroke()
}

View file

@ -1,167 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"bytes"
"fmt"
"image"
_ "image/jpeg" // to be able to decode jpegs
_ "image/png" // to be able to decode pngs
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"github.com/Wessie/appdirs"
)
// TileFetcher downloads map tile images from a TileProvider
type TileFetcher struct {
tileProvider *TileProvider
cacheDir string
useCaching bool
userAgent string
}
// NewTileFetcher creates a new Tilefetcher struct
func NewTileFetcher(tileProvider *TileProvider) *TileFetcher {
t := new(TileFetcher)
t.tileProvider = tileProvider
app := appdirs.New("go-staticmaps", "flopp.net", "0.1")
t.cacheDir = fmt.Sprintf("%s/%s", app.UserCache(), tileProvider.Name)
t.useCaching = true
t.userAgent = "Mozilla/5.0+(compatible; go-staticmaps/0.1; https://github.com/flopp/go-staticmaps)"
return t
}
// SetUserAgent sets the HTTP user agent string used when downloading map tiles
func (t *TileFetcher) SetUserAgent(a string) {
t.userAgent = a
}
func (t *TileFetcher) url(zoom, x, y int) string {
shard := ""
ss := len(t.tileProvider.Shards)
if len(t.tileProvider.Shards) > 0 {
shard = t.tileProvider.Shards[(x+y)%ss]
}
return t.tileProvider.getURL(shard, zoom, x, y)
}
func (t *TileFetcher) cacheFileName(zoom int, x, y int) string {
return fmt.Sprintf("%s/%d/%d/%d", t.cacheDir, zoom, x, y)
}
// ToggleCaching enables/disables caching
func (t *TileFetcher) ToggleCaching(enabled bool) {
t.useCaching = enabled
}
// Fetch download (or retrieves from the cache) a tile image for the specified zoom level and tile coordinates
func (t *TileFetcher) Fetch(zoom, x, y int) (image.Image, error) {
if t.useCaching {
fileName := t.cacheFileName(zoom, x, y)
cachedImg, err := t.loadCache(fileName)
if err == nil {
return cachedImg, nil
}
}
url := t.url(zoom, x, y)
data, err := t.download(url)
if err != nil {
return nil, err
}
img, _, err := image.Decode(bytes.NewBuffer(data))
if err != nil {
return nil, err
}
if t.useCaching {
fileName := t.cacheFileName(zoom, x, y)
if err := t.storeCache(fileName, data); err != nil {
log.Printf("Failed to store map tile as '%s': %s", fileName, err)
}
}
return img, nil
}
func (t *TileFetcher) download(url string) ([]byte, error) {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", t.userAgent)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("GET %s: %s", url, resp.Status)
}
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return contents, nil
}
func (t *TileFetcher) loadCache(fileName string) (image.Image, error) {
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
return img, nil
}
func (t *TileFetcher) createCacheDir(path string) error {
src, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return os.MkdirAll(path, 0777)
}
return err
}
if src.IsDir() {
return nil
}
return fmt.Errorf("File exists but is not a directory: %s", path)
}
func (t *TileFetcher) storeCache(fileName string, data []byte) error {
dir, _ := filepath.Split(fileName)
if err := t.createCacheDir(dir); err != nil {
return err
}
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
if _, err = io.Copy(file, bytes.NewBuffer(data)); err != nil {
return err
}
return nil
}

View file

@ -1,158 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import "fmt"
// TileProvider encapsulates all infos about a map tile provider service (name, url scheme, attribution, etc.)
type TileProvider struct {
Name string
Attribution string
TileSize int
URLPattern string // "%[1]s" => shard, "%[2]d" => zoom, "%[3]d" => x, "%[4]d" => y
Shards []string
}
func (t *TileProvider) getURL(shard string, zoom, x, y int) string {
return fmt.Sprintf(t.URLPattern, shard, zoom, x, y)
}
// NewTileProviderOpenStreetMaps creates a TileProvider struct for OSM's tile service
func NewTileProviderOpenStreetMaps() *TileProvider {
t := new(TileProvider)
t.Name = "osm"
t.Attribution = "Maps and Data (c) openstreetmap.org and contributors, ODbL"
t.TileSize = 256
t.URLPattern = "http://%[1]s.tile.openstreetmap.org/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b", "c"}
return t
}
func newTileProviderThunderforest(name string) *TileProvider {
t := new(TileProvider)
t.Name = fmt.Sprintf("thunderforest-%s", name)
t.Attribution = "Maps (c) Thundeforest; Data (c) OSM and contributors, ODbL"
t.TileSize = 256
t.URLPattern = "https://%[1]s.tile.thunderforest.com/" + name + "/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b", "c"}
return t
}
// NewTileProviderThunderforestLandscape creates a TileProvider struct for thundeforests's 'landscape' tile service
func NewTileProviderThunderforestLandscape() *TileProvider {
return newTileProviderThunderforest("landscape")
}
// NewTileProviderThunderforestOutdoors creates a TileProvider struct for thundeforests's 'outdoors' tile service
func NewTileProviderThunderforestOutdoors() *TileProvider {
return newTileProviderThunderforest("outdoors")
}
// NewTileProviderThunderforestTransport creates a TileProvider struct for thundeforests's 'transport' tile service
func NewTileProviderThunderforestTransport() *TileProvider {
return newTileProviderThunderforest("transport")
}
// NewTileProviderStamenToner creates a TileProvider struct for stamens' 'toner' tile service
func NewTileProviderStamenToner() *TileProvider {
t := new(TileProvider)
t.Name = "stamen-toner"
t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL"
t.TileSize = 256
t.URLPattern = "http://%[1]s.tile.stamen.com/toner/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b", "c", "d"}
return t
}
// NewTileProviderStamenTerrain creates a TileProvider struct for stamens' 'terrain' tile service
func NewTileProviderStamenTerrain() *TileProvider {
t := new(TileProvider)
t.Name = "stamen-terrain"
t.Attribution = "Maps (c) Stamen; Data (c) OSM and contributors, ODbL"
t.TileSize = 256
t.URLPattern = "http://%[1]s.tile.stamen.com/terrain/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b", "c", "d"}
return t
}
// NewTileProviderOpenTopoMap creates a TileProvider struct for opentopomap's tile service
func NewTileProviderOpenTopoMap() *TileProvider {
t := new(TileProvider)
t.Name = "opentopomap"
t.Attribution = "Maps (c) OpenTopoMap [CC-BY-SA]; Data (c) OSM and contributors [ODbL]; Data (c) SRTM"
t.TileSize = 256
t.URLPattern = "http://%[1]s.tile.opentopomap.org/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b", "c"}
return t
}
// NewTileProviderWikimedia creates a TileProvider struct for Wikimedia's tile service
func NewTileProviderWikimedia() *TileProvider {
t := new(TileProvider)
t.Name = "wikimedia"
t.Attribution = "Map (c) Wikimedia; Data (c) OSM and contributors, ODbL."
t.TileSize = 256
t.URLPattern = "https://maps.wikimedia.org/osm-intl/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{}
return t
}
// NewTileProviderOpenCycleMap creates a TileProvider struct for OpenCycleMap's tile service
func NewTileProviderOpenCycleMap() *TileProvider {
t := new(TileProvider)
t.Name = "cycle"
t.Attribution = "Maps and Data (c) openstreetmaps.org and contributors, ODbL"
t.TileSize = 256
t.URLPattern = "http://%[1]s.tile.opencyclemap.org/cycle/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b"}
return t
}
func newTileProviderCarto(name string) *TileProvider {
t := new(TileProvider)
t.Name = fmt.Sprintf("carto-%s", name)
t.Attribution = "Map (c) Carto [CC BY 3.0] Data (c) OSM and contributors, ODbL."
t.TileSize = 256
t.URLPattern = "https://cartodb-basemaps-%[1]s.global.ssl.fastly.net/" + name + "_all/%[2]d/%[3]d/%[4]d.png"
t.Shards = []string{"a", "b", "c", "d"}
return t
}
// NewTileProviderCartoLight creates a TileProvider struct for Carto's tile service (light variant)
func NewTileProviderCartoLight() *TileProvider {
return newTileProviderCarto("light")
}
// NewTileProviderCartoDark creates a TileProvider struct for Carto's tile service (dark variant)
func NewTileProviderCartoDark() *TileProvider {
return newTileProviderCarto("dark")
}
// GetTileProviders returns a map of all available TileProviders
func GetTileProviders() map[string]*TileProvider {
m := make(map[string]*TileProvider)
list := []*TileProvider{
NewTileProviderOpenStreetMaps(),
NewTileProviderOpenCycleMap(),
NewTileProviderThunderforestLandscape(),
NewTileProviderThunderforestOutdoors(),
NewTileProviderThunderforestTransport(),
NewTileProviderStamenToner(),
NewTileProviderStamenTerrain(),
NewTileProviderOpenTopoMap(),
NewTileProviderOpenStreetMaps(),
NewTileProviderOpenCycleMap(),
NewTileProviderCartoLight(),
NewTileProviderCartoDark(),
}
for _, tp := range list {
m[tp.Name] = tp
}
return m
}

View file

@ -1,18 +0,0 @@
// Copyright 2016, 2017 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package sm
import (
"strings"
)
// hasPrefix checks if 's' has prefix 'prefix'; returns 'true' and the remainder on success, and 'false', 's' otherwise.
func hasPrefix(s string, prefix string) (bool, string) {
if strings.HasPrefix(s, prefix) {
return true, strings.TrimPrefix(s, prefix)
}
return false, s
}

View file

@ -1,37 +0,0 @@
package accessLogger
import (
"fmt"
"net/http"
"strconv"
)
type AccessLogResponseWriter struct {
StatusCode int
Size int
http.ResponseWriter
}
func New(res http.ResponseWriter) *AccessLogResponseWriter {
return &AccessLogResponseWriter{
StatusCode: 200,
Size: 0,
ResponseWriter: res,
}
}
func (a *AccessLogResponseWriter) Write(out []byte) (int, error) {
s, err := a.ResponseWriter.Write(out)
a.Size += s
return s, err
}
func (a *AccessLogResponseWriter) WriteHeader(code int) {
a.StatusCode = code
a.ResponseWriter.WriteHeader(code)
}
func (a *AccessLogResponseWriter) HTTPResponseType() string {
return fmt.Sprintf("%cxx", strconv.FormatInt(int64(a.StatusCode), 10)[0])
}

View file

@ -1,55 +0,0 @@
package http
import (
"crypto/md5"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http"
"strings"
"github.com/Luzifer/go_helpers/str"
)
func GetDigestAuth(resp *http.Response, method, requestPath, user, password string) string {
params := map[string]string{}
for _, part := range strings.Split(resp.Header.Get("Www-Authenticate"), " ") {
if !strings.Contains(part, `="`) {
continue
}
spl := strings.Split(strings.Trim(part, " ,"), "=")
if !str.StringInSlice(spl[0], []string{"nonce", "realm", "qop"}) {
continue
}
params[spl[0]] = strings.Trim(spl[1], `"`)
}
b := make([]byte, 8)
io.ReadFull(rand.Reader, b)
params["cnonce"] = fmt.Sprintf("%x", b)
params["nc"] = "1"
params["uri"] = requestPath
params["username"] = user
params["response"] = getMD5([]string{
getMD5([]string{params["username"], params["realm"], password}),
params["nonce"],
params["nc"],
params["cnonce"],
params["qop"],
getMD5([]string{method, requestPath}),
})
authParts := []string{}
for k, v := range params {
authParts = append(authParts, fmt.Sprintf("%s=%q", k, v))
}
return "Digest " + strings.Join(authParts, ", ")
}
func getMD5(in []string) string {
h := md5.New()
h.Write([]byte(strings.Join(in, ":")))
return hex.EncodeToString(h.Sum(nil))
}

View file

@ -1,52 +0,0 @@
package http
import (
"log"
"net/http"
"strings"
"time"
"github.com/Luzifer/go_helpers/accessLogger"
)
type HTTPLogHandler struct {
Handler http.Handler
TrustedIPHeaders []string
}
func NewHTTPLogHandler(h http.Handler) http.Handler {
return HTTPLogHandler{
Handler: h,
TrustedIPHeaders: []string{"X-Forwarded-For", "RemoteAddr", "X-Real-IP"},
}
}
func (l HTTPLogHandler) ServeHTTP(res http.ResponseWriter, r *http.Request) {
start := time.Now()
ares := accessLogger.New(res)
l.Handler.ServeHTTP(ares, r)
log.Printf("%s - \"%s %s\" %d %d \"%s\" \"%s\" %s",
l.findIP(r),
r.Method,
r.URL.Path,
ares.StatusCode,
ares.Size,
r.Header.Get("Referer"),
r.Header.Get("User-Agent"),
time.Since(start),
)
}
func (l HTTPLogHandler) findIP(r *http.Request) string {
remoteAddr := strings.SplitN(r.RemoteAddr, ":", 2)[0]
for _, hdr := range l.TrustedIPHeaders {
if value := r.Header.Get(hdr); value != "" {
return strings.SplitN(value, ",", 2)[0]
}
}
return remoteAddr
}

View file

@ -1,21 +0,0 @@
package str
// AppendIfMissing adds a string to a slice when it's not present yet
func AppendIfMissing(slice []string, s string) []string {
for _, e := range slice {
if e == s {
return slice
}
}
return append(slice, s)
}
// StringInSlice checks for the existence of a string in the slice
func StringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

View file

@ -1,8 +0,0 @@
language: go
go:
- 1.6
- 1.7
- tip
script: go test -v -race -cover ./...

View file

@ -1,9 +0,0 @@
# 1.2.0 / 2017-06-19
* Add ParseAndValidate method
# 1.1.0 / 2016-06-28
* Support time.Duration config parameters
* Added goreportcard badge
* Added testcase for using bool with ENV and default

View file

@ -1,13 +0,0 @@
Copyright 2015 Knut Ahlers <knut@ahlers.me>
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.

View file

@ -1,87 +0,0 @@
[![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig)
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig)
[![Go Report](http://goreportcard.com/badge/Luzifer/rconfig)](http://goreportcard.com/report/Luzifer/rconfig)
## Description
> Package rconfig implements a CLI configuration reader with struct-embedded defaults, environment variables and posix compatible flag parsing using the [pflag](https://github.com/spf13/pflag) library.
## Installation
Install by running:
```
go get -u github.com/Luzifer/rconfig
```
OR fetch a specific version:
```
go get -u gopkg.in/luzifer/rconfig.v1
```
Run tests by running:
```
go test -v -race -cover github.com/Luzifer/rconfig
```
## Usage
A very simple usecase is to just configure a struct inside the vars section of your `main.go` and to parse the commandline flags from the `main()` function:
```go
package main
import (
"fmt"
"github.com/Luzifer/rconfig"
)
var (
cfg = struct {
Username string `default:"unknown" flag:"user" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
}
}{}
)
func main() {
rconfig.Parse(&cfg)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
cfg.Username,
cfg.Details.Age)
}
```
### Provide variable defaults by using a file
Given you have a file `~/.myapp.yml` containing some secrets or usernames (for the example below username is assumed to be "luzifer") as a default configuration for your application you can use this source code to load the defaults from that file using the `vardefault` tag in your configuration struct.
The order of the directives (lower number = higher precedence):
1. Flags provided in command line
1. Environment variables
1. Variable defaults (`vardefault` tag in the struct)
1. `default` tag in the struct
```go
var cfg = struct {
Username string `vardefault:"username" flag:"username" description:"Your username"`
}
func main() {
rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml"))
rconfig.Parse(&cfg)
fmt.Printf("Username = %s", cfg.Username)
// Output: Username = luzifer
}
```
## More info
You can see the full reference documentation of the rconfig package [at godoc.org](https://godoc.org/github.com/Luzifer/rconfig), or through go's standard documentation system by running `godoc -http=:6060` and browsing to [http://localhost:6060/pkg/github.com/Luzifer/rconfig](http://localhost:6060/pkg/github.com/Luzifer/rconfig) after installation.

View file

@ -1,356 +0,0 @@
// Package rconfig implements a CLI configuration reader with struct-embedded
// defaults, environment variables and posix compatible flag parsing using
// the pflag library.
package rconfig
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"time"
"github.com/spf13/pflag"
validator "gopkg.in/validator.v2"
)
var (
fs *pflag.FlagSet
variableDefaults map[string]string
)
func init() {
variableDefaults = make(map[string]string)
}
// Parse takes the pointer to a struct filled with variables which should be read
// from ENV, default or flag. The precedence in this is flag > ENV > default. So
// if a flag is specified on the CLI it will overwrite the ENV and otherwise ENV
// overwrites the default specified.
//
// For your configuration struct you can use the following struct-tags to control
// the behavior of rconfig:
//
// default: Set a default value
// vardefault: Read the default value from the variable defaults
// env: Read the value from this environment variable
// flag: Flag to read in format "long,short" (for example "listen,l")
// description: A help text for Usage output to guide your users
//
// The format you need to specify those values you can see in the example to this
// function.
//
func Parse(config interface{}) error {
return parse(config, nil)
}
// ParseAndValidate works exactly like Parse but implements an additional run of
// the go-validator package on the configuration struct. Therefore additonal struct
// tags are supported like described in the readme file of the go-validator package:
//
// https://github.com/go-validator/validator/tree/v2#usage
func ParseAndValidate(config interface{}) error {
return parseAndValidate(config, nil)
}
// Args returns the non-flag command-line arguments.
func Args() []string {
return fs.Args()
}
// Usage prints a basic usage with the corresponding defaults for the flags to
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
func Usage() {
if fs != nil && fs.Parsed() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fs.PrintDefaults()
}
}
// SetVariableDefaults presets the parser with a map of default values to be used
// when specifying the vardefault tag
func SetVariableDefaults(defaults map[string]string) {
variableDefaults = defaults
}
func parseAndValidate(in interface{}, args []string) error {
if err := parse(in, args); err != nil {
return err
}
return validator.Validate(in)
}
func parse(in interface{}, args []string) error {
if args == nil {
args = os.Args
}
fs = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError)
if err := execTags(in, fs); err != nil {
return err
}
return fs.Parse(args)
}
func execTags(in interface{}, fs *pflag.FlagSet) error {
if reflect.TypeOf(in).Kind() != reflect.Ptr {
return errors.New("Calling parser with non-pointer")
}
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
return errors.New("Calling parser with pointer to non-struct")
}
st := reflect.ValueOf(in).Elem()
for i := 0; i < st.NumField(); i++ {
valField := st.Field(i)
typeField := st.Type().Field(i)
if typeField.Tag.Get("default") == "" && typeField.Tag.Get("env") == "" && typeField.Tag.Get("flag") == "" && typeField.Type.Kind() != reflect.Struct {
// None of our supported tags is present and it's not a sub-struct
continue
}
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
value = envDefault(typeField.Tag.Get("env"), value)
parts := strings.Split(typeField.Tag.Get("flag"), ",")
switch typeField.Type {
case reflect.TypeOf(time.Duration(0)):
v, err := time.ParseDuration(value)
if err != nil {
if value == "" {
v = time.Duration(0)
} else {
return err
}
}
if typeField.Tag.Get("flag") != "" {
if len(parts) == 1 {
fs.DurationVar(valField.Addr().Interface().(*time.Duration), parts[0], v, typeField.Tag.Get("description"))
} else {
fs.DurationVarP(valField.Addr().Interface().(*time.Duration), parts[0], parts[1], v, typeField.Tag.Get("description"))
}
} else {
valField.Set(reflect.ValueOf(v))
}
continue
}
switch typeField.Type.Kind() {
case reflect.String:
if typeField.Tag.Get("flag") != "" {
if len(parts) == 1 {
fs.StringVar(valField.Addr().Interface().(*string), parts[0], value, typeField.Tag.Get("description"))
} else {
fs.StringVarP(valField.Addr().Interface().(*string), parts[0], parts[1], value, typeField.Tag.Get("description"))
}
} else {
valField.SetString(value)
}
case reflect.Bool:
v := value == "true"
if typeField.Tag.Get("flag") != "" {
if len(parts) == 1 {
fs.BoolVar(valField.Addr().Interface().(*bool), parts[0], v, typeField.Tag.Get("description"))
} else {
fs.BoolVarP(valField.Addr().Interface().(*bool), parts[0], parts[1], v, typeField.Tag.Get("description"))
}
} else {
valField.SetBool(v)
}
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
vt, err := strconv.ParseInt(value, 10, 64)
if err != nil {
if value == "" {
vt = 0
} else {
return err
}
}
if typeField.Tag.Get("flag") != "" {
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
} else {
valField.SetInt(vt)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
vt, err := strconv.ParseUint(value, 10, 64)
if err != nil {
if value == "" {
vt = 0
} else {
return err
}
}
if typeField.Tag.Get("flag") != "" {
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
} else {
valField.SetUint(vt)
}
case reflect.Float32, reflect.Float64:
vt, err := strconv.ParseFloat(value, 64)
if err != nil {
if value == "" {
vt = 0.0
} else {
return err
}
}
if typeField.Tag.Get("flag") != "" {
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
} else {
valField.SetFloat(vt)
}
case reflect.Struct:
if err := execTags(valField.Addr().Interface(), fs); err != nil {
return err
}
case reflect.Slice:
switch typeField.Type.Elem().Kind() {
case reflect.Int:
def := []int{}
for _, v := range strings.Split(value, ",") {
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
if err != nil {
return err
}
def = append(def, int(it))
}
if len(parts) == 1 {
fs.IntSliceVar(valField.Addr().Interface().(*[]int), parts[0], def, typeField.Tag.Get("description"))
} else {
fs.IntSliceVarP(valField.Addr().Interface().(*[]int), parts[0], parts[1], def, typeField.Tag.Get("description"))
}
case reflect.String:
del := typeField.Tag.Get("delimiter")
if len(del) == 0 {
del = ","
}
def := strings.Split(value, del)
if len(parts) == 1 {
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
} else {
fs.StringSliceVarP(valField.Addr().Interface().(*[]string), parts[0], parts[1], def, typeField.Tag.Get("description"))
}
}
}
}
return nil
}
func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt float64, desc string) {
switch t {
case reflect.Float32:
if len(parts) == 1 {
fs.Float32Var(field.(*float32), parts[0], float32(vt), desc)
} else {
fs.Float32VarP(field.(*float32), parts[0], parts[1], float32(vt), desc)
}
case reflect.Float64:
if len(parts) == 1 {
fs.Float64Var(field.(*float64), parts[0], float64(vt), desc)
} else {
fs.Float64VarP(field.(*float64), parts[0], parts[1], float64(vt), desc)
}
}
}
func registerFlagInt(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt int64, desc string) {
switch t {
case reflect.Int:
if len(parts) == 1 {
fs.IntVar(field.(*int), parts[0], int(vt), desc)
} else {
fs.IntVarP(field.(*int), parts[0], parts[1], int(vt), desc)
}
case reflect.Int8:
if len(parts) == 1 {
fs.Int8Var(field.(*int8), parts[0], int8(vt), desc)
} else {
fs.Int8VarP(field.(*int8), parts[0], parts[1], int8(vt), desc)
}
case reflect.Int32:
if len(parts) == 1 {
fs.Int32Var(field.(*int32), parts[0], int32(vt), desc)
} else {
fs.Int32VarP(field.(*int32), parts[0], parts[1], int32(vt), desc)
}
case reflect.Int64:
if len(parts) == 1 {
fs.Int64Var(field.(*int64), parts[0], int64(vt), desc)
} else {
fs.Int64VarP(field.(*int64), parts[0], parts[1], int64(vt), desc)
}
}
}
func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts []string, vt uint64, desc string) {
switch t {
case reflect.Uint:
if len(parts) == 1 {
fs.UintVar(field.(*uint), parts[0], uint(vt), desc)
} else {
fs.UintVarP(field.(*uint), parts[0], parts[1], uint(vt), desc)
}
case reflect.Uint8:
if len(parts) == 1 {
fs.Uint8Var(field.(*uint8), parts[0], uint8(vt), desc)
} else {
fs.Uint8VarP(field.(*uint8), parts[0], parts[1], uint8(vt), desc)
}
case reflect.Uint16:
if len(parts) == 1 {
fs.Uint16Var(field.(*uint16), parts[0], uint16(vt), desc)
} else {
fs.Uint16VarP(field.(*uint16), parts[0], parts[1], uint16(vt), desc)
}
case reflect.Uint32:
if len(parts) == 1 {
fs.Uint32Var(field.(*uint32), parts[0], uint32(vt), desc)
} else {
fs.Uint32VarP(field.(*uint32), parts[0], parts[1], uint32(vt), desc)
}
case reflect.Uint64:
if len(parts) == 1 {
fs.Uint64Var(field.(*uint64), parts[0], uint64(vt), desc)
} else {
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], uint64(vt), desc)
}
}
}
func envDefault(env, def string) string {
value := def
if env != "" {
if e := os.Getenv(env); e != "" {
value = e
}
}
return value
}
func varDefault(name, def string) string {
value := def
if name != "" {
if v, ok := variableDefaults[name]; ok {
value = v
}
}
return value
}

View file

@ -1,27 +0,0 @@
package rconfig
import (
"io/ioutil"
"gopkg.in/yaml.v2"
)
// VarDefaultsFromYAMLFile reads contents of a file and calls VarDefaultsFromYAML
func VarDefaultsFromYAMLFile(filename string) map[string]string {
data, err := ioutil.ReadFile(filename)
if err != nil {
return make(map[string]string)
}
return VarDefaultsFromYAML(data)
}
// VarDefaultsFromYAML creates a vardefaults map from YAML raw data
func VarDefaultsFromYAML(in []byte) map[string]string {
out := make(map[string]string)
err := yaml.Unmarshal(in, &out)
if err != nil {
return make(map[string]string)
}
return out
}

View file

View file

@ -1,18 +0,0 @@
Copyright (c) 2013 Wesley Bitter
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,3 +0,0 @@
This is a port of the excellent python module named the same, which can be found here [appdirs](https://github.com/ActiveState/appdirs).
The README and documentation in the original should be a good starting point. Documentation is currently lacking on the port itself, but the API is very similar to the python version.

View file

@ -1,80 +0,0 @@
// A port of the excellent python module `appdirs`.
// See https://github.com/ActiveState/appdirs for the python version.
package appdirs
import (
"os/user"
"strings"
)
// App is a helper type to create easy access across your program to the appdirs
// functions.
//
// The *App type has 6 methods that map to the 6 functions exported by `appdirs`.
// All methods take no arguments, and supply the function it wraps with arguments
// pre-set in the struct on creation.
type App struct {
Name string
Author string
Version string
Roaming bool
Opinion bool
}
// New returns a new App helper that has various methods for receiving
// relevant directories for your application.
//
// The following defaults are used for the two fields not settable by New:
// Roaming: false, Opinion: true
//
// If you want to set these, create your own App struct by the usual means.
func New(name, author, version string) *App {
return &App{
Name: name,
Author: author,
Version: version,
Roaming: false,
Opinion: true,
}
}
// UserData returns the full path to the user-specific data directory
func (app *App) UserData() string {
return UserDataDir(app.Name, app.Author, app.Version, app.Roaming)
}
// SiteData returns the full path to the user-shared data directory
func (app *App) SiteData() string {
return SiteDataDir(app.Name, app.Author, app.Version)
}
// SiteConfig returns the full path to the user-shared configuration directory
func (app *App) SiteConfig() string {
return SiteConfigDir(app.Name, app.Author, app.Version)
}
// UserConfig returns the full path to the user-specific configuration directory
func (app *App) UserConfig() string {
return UserConfigDir(app.Name, app.Author, app.Version, app.Roaming)
}
// UserCache returns the full path to the user-specific cache directory
func (app *App) UserCache() string {
return UserCacheDir(app.Name, app.Author, app.Version, app.Opinion)
}
// UserLog returns the full path to the user-specific log directory
func (app *App) UserLog() string {
return UserLogDir(app.Name, app.Author, app.Version, app.Opinion)
}
// ExpandUser is a helper function that expands the first '~' it finds in the
// passed path with the home directory of the current user.
//
// Note: This only works on environments similar to bash.
func ExpandUser(path string) string {
if u, err := user.Current(); err == nil {
return strings.Replace(path, "~", u.HomeDir, -1)
}
return path
}

View file

@ -1,63 +0,0 @@
package appdirs
import (
"path/filepath"
)
func userDataDir(name, author, version string, roaming bool) (path string) {
path = ExpandUser("~/Library/Application Support")
if name != "" {
path = filepath.Join(path, name)
}
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}
func siteDataDir(name, author, version string) (path string) {
path = ExpandUser("/Library/Application Support")
if name != "" {
path = filepath.Join(path, name)
}
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}
func userConfigDir(name, author, version string, roaming bool) (path string) {
return UserDataDir(name, author, version, roaming)
}
func siteConfigDir(name, author, version string) (path string) {
return SiteDataDir(name, author, version)
}
func userCacheDir(name, author, version string, opinion bool) (path string) {
path = ExpandUser("~/Library/Caches")
if name != "" {
path = filepath.Join(path, name)
}
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}
func userLogDir(name, author, version string, opinion bool) (path string) {
path = ExpandUser("~/Library/Logs")
path = filepath.Join(path, name)
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}

View file

@ -1,102 +0,0 @@
// +build linux freebsd netbsd openbsd
package appdirs
import (
"os"
"path/filepath"
)
func userDataDir(name, author, version string, roaming bool) (path string) {
if path = os.Getenv("XDG_DATA_HOME"); path == "" {
path = ExpandUser("~/.local/share")
}
if name != "" {
path = filepath.Join(path, name, version)
}
return path
}
func SiteDataDirs(name, author, version string) (paths []string) {
var path string
if path = os.Getenv("XDG_DATA_DIRS"); path == "" {
paths = []string{"/usr/local/share", "/usr/share"}
} else {
paths = filepath.SplitList(path)
}
for i, path := range paths {
path = ExpandUser(path)
if name != "" {
path = filepath.Join(path, name, version)
}
paths[i] = path
}
return paths
}
func siteDataDir(name, author, version string) (path string) {
return SiteDataDirs(name, author, version)[0]
}
func userConfigDir(name, author, version string, roaming bool) (path string) {
if path = os.Getenv("XDG_CONFIG_HOME"); path == "" {
path = ExpandUser("~/.config")
}
if name != "" {
path = filepath.Join(path, name, version)
}
return path
}
func SiteConfigDirs(name, author, version string) (paths []string) {
var path string
if path = os.Getenv("XDG_CONFIG_DIRS"); path == "" {
paths = []string{"/etc/xdg"}
} else {
paths = filepath.SplitList(path)
}
for i, path := range paths {
path = ExpandUser(path)
if name != "" {
path = filepath.Join(path, name, version)
}
paths[i] = path
}
return paths
}
func siteConfigDir(name, author, version string) (path string) {
return SiteConfigDirs(name, author, version)[0]
}
func userCacheDir(name, author, version string, opinion bool) (path string) {
if path = os.Getenv("XDG_CACHE_HOME"); path == "" {
path = ExpandUser("~/.cache")
}
if name != "" {
path = filepath.Join(path, name, version)
}
return path
}
func userLogDir(name, author, version string, opinion bool) (path string) {
path = UserCacheDir(name, author, version, opinion)
return filepath.Join(path, "log")
}

View file

@ -1,171 +0,0 @@
package appdirs
import (
"path/filepath"
"syscall"
"unsafe"
)
var (
shell32, _ = syscall.LoadLibrary("shell32.dll")
getKnownFolderPath, _ = syscall.GetProcAddress(shell32, "SHGetKnownFolderPath")
ole32, _ = syscall.LoadLibrary("Ole32.dll")
coTaskMemFree, _ = syscall.GetProcAddress(ole32, "CoTaskMemFree")
)
// These are KNOWNFOLDERID constants that are passed to GetKnownFolderPath
var (
rfidLocalAppData = syscall.GUID{
0xf1b32785,
0x6fba,
0x4fcf,
[8]byte{0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91},
}
rfidRoamingAppData = syscall.GUID{
0x3eb685db,
0x65f9,
0x4cf6,
[8]byte{0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d},
}
rfidProgramData = syscall.GUID{
0x62ab5d82,
0xfdc1,
0x4dc3,
[8]byte{0xa9, 0xdd, 0x07, 0x0d, 0x1d, 0x49, 0x5d, 0x97},
}
)
func userDataDir(name, author, version string, roaming bool) (path string) {
if author == "" {
author = name
}
var rfid syscall.GUID
if roaming {
rfid = rfidRoamingAppData
} else {
rfid = rfidLocalAppData
}
path, err := getFolderPath(rfid)
if err != nil {
return ""
}
if path, err = filepath.Abs(path); err != nil {
return ""
}
if name != "" {
path = filepath.Join(path, author, name)
}
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}
func siteDataDir(name, author, version string) (path string) {
path, err := getFolderPath(rfidProgramData)
if err != nil {
return ""
}
if path, err = filepath.Abs(path); err != nil {
return ""
}
if author == "" {
author = name
}
if name != "" {
path = filepath.Join(path, author, name)
}
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}
func userConfigDir(name, author, version string, roaming bool) string {
return UserDataDir(name, author, version, roaming)
}
func siteConfigDir(name, author, version string) (path string) {
return SiteDataDir(name, author, version)
}
func userCacheDir(name, author, version string, opinion bool) (path string) {
if author == "" {
author = name
}
path, err := getFolderPath(rfidLocalAppData)
if err != nil {
return ""
}
if path, err = filepath.Abs(path); err != nil {
return ""
}
if name != "" {
path = filepath.Join(path, author, name)
if opinion {
path = filepath.Join(path, "Cache")
}
}
if name != "" && version != "" {
path = filepath.Join(path, version)
}
return path
}
func userLogDir(name, author, version string, opinion bool) (path string) {
path = UserDataDir(name, author, version, false)
if opinion {
path = filepath.Join(path, "Logs")
}
return path
}
func getFolderPath(rfid syscall.GUID) (string, error) {
var res uintptr
ret, _, callErr := syscall.Syscall6(
uintptr(getKnownFolderPath),
4,
uintptr(unsafe.Pointer(&rfid)),
0,
0,
uintptr(unsafe.Pointer(&res)),
0,
0,
)
if callErr != 0 && ret != 0 {
return "", callErr
}
defer syscall.Syscall(uintptr(coTaskMemFree), 1, res, 0, 0)
return ucs2PtrToString(res), nil
}
func ucs2PtrToString(p uintptr) string {
ptr := (*[4096]uint16)(unsafe.Pointer(p))
return syscall.UTF16ToString((*ptr)[:])
}

View file

@ -1,108 +0,0 @@
// appdirs project doc.go
/*
This is a port of a python module used for finding what directory you 'should'
be using for saving your application data such as configuration, cache files or
other files.
The location of these directories is often hard to get right. The original python
module set out to change this into a simple API that returns you the exact
directory you need. This is a port of it to Go.
Depending on platform, this package exports you at the least 6 functions that
return various system directories. And one helper struct type that combines the
functions into methods for less arguments in your code.
Each function defined accepts a number of arguments, each argument is optional
and can be left to the types default value if omitted. Often the function will
ignore arguments if the name given is empty.
Passing in all default values into any of the functions will return you the base
directory without any of the arguments appended to it.
*/
package appdirs
// UserDataDir returns the full path to the user-specific data directory.
//
// This function uses XDG_DATA_HOME as defined by the XDG spec on *nix like systems.
//
// Examples of return values:
// Mac OS X: ~/Library/Application Support/<AppName>
// Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
// Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
// Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
// Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
// Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
func UserDataDir(name, author, version string, roaming bool) string {
return userDataDir(name, author, version, roaming)
}
// SiteDataDir returns the full path to the user-shared data directory.
//
// This function uses XDG_DATA_DIRS[0] as by the XDG spec on *nix like systems.
//
// Examples of return values:
// Mac OS X: /Library/Application Support/<AppName>
// Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
// Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
// Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
// Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
//
// WARNING: Do not use this on Windows Vista, See the note above.
func SiteDataDir(name, author, version string) string {
return siteDataDir(name, author, version)
}
// UserConfigDir returns the full path to the user-specific configuration directory
//
// This function uses XDG_CONFIG_HOME as by the XDG spec on *nix like systems.
//
// Examples of return values:
// Mac OS X: same as UserDataDir
// Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
// Win *: same as UserDataDir
func UserConfigDir(name, author, version string, roaming bool) string {
return userConfigDir(name, author, version, roaming)
}
// SiteConfigDir returns the full path to the user-shared data directory.
//
// This function uses XDG_CONFIG_DIRS[0] as by the XDG spec on *nix like systems.
//
// Examples of return values:
// Mac OS X: same as SiteDataDir
// Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in $XDG_CONFIG_DIRS
// Win *: same as SiteDataDir
// Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
//
// WARNING: Do not use this on Windows Vista, see the note above.
func SiteConfigDir(name, author, version string) string {
return siteConfigDir(name, author, version)
}
// UserCacheDir returns the full path to the user-specific cache directory.
//
// The opinion argument will append 'Cache' to the base directory if set to true.
//
// Examples of return values:
// Mac OS X: ~/Library/Caches/<AppName>
// Unix: ~/.cache/<AppName> (XDG default)
// Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
// Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
func UserCacheDir(name, author, version string, opinion bool) string {
return userCacheDir(name, author, version, opinion)
}
// UserLogDir returns the full path to the user-specific log directory.
//
// The opinion argument will append either 'Logs' (windows) or 'log' (unix) to
// the base directory when set to true.
//
// Examples of return values:
// Mac OS X: ~/Library/Logs/<AppName>
// Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
// Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
// Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
func UserLogDir(name, author, version string, opinion bool) string {
return userLogDir(name, author, version, opinion)
}

View file

@ -1,2 +0,0 @@
/debug
/.vscode

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Didip Kerabat
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,164 +0,0 @@
[![GoDoc](https://godoc.org/github.com/didip/tollbooth?status.svg)](http://godoc.org/github.com/didip/tollbooth)
[![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/didip/tollbooth/master/LICENSE)
## Tollbooth
This is a generic middleware to rate-limit HTTP requests.
**NOTE 1:** This library is considered finished.
**NOTE 2:** In the coming weeks, I will be removing thirdparty modules and moving them to their own dedicated repos.
**NOTE 3:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API.
## Versions
**v1.0.0:** This version maintains the old API but all of the thirdparty modules are moved to their own repo.
**v2.x.x:** Brand new API for the sake of code cleanup, thread safety, & auto-expiring data structures.
**v3.x.x:** Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limit X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth.
## Five Minutes Tutorial
```go
package main
import (
"github.com/didip/tollbooth"
"net/http"
"time"
)
func HelloHandler(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello, World!"))
}
func main() {
// Create a request limiter per handler.
http.Handle("/", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, nil), HelloHandler))
http.ListenAndServe(":12345", nil)
}
```
## Features
1. Rate-limit by request's remote IP, path, methods, custom headers, & basic auth usernames.
```go
import (
"time"
"github.com/didip/tollbooth/limiter"
)
lmt := tollbooth.NewLimiter(1, nil)
// or create a limiter with expirable token buckets
// This setting means:
// create a 1 request/second limiter and
// every token bucket in it will expire 1 hour after it was initially set.
lmt = tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
// Configure list of places to look for IP address.
// By default it's: "RemoteAddr", "X-Forwarded-For", "X-Real-IP"
// If your application is behind a proxy, set "X-Forwarded-For" first.
lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"})
// Limit only GET and POST requests.
lmt.SetMethods([]string{"GET", "POST"})
// Limit based on basic auth usernames.
// You add them on-load, or later as you handle requests.
lmt.SetBasicAuthUsers([]string{"bob", "jane", "didip", "vip"})
// You can remove them later as well.
lmt.RemoveBasicAuthUsers([]string{"vip"})
// Limit request headers containing certain values.
// You add them on-load, or later as you handle requests.
lmt.SetHeader("X-Access-Token", []string{"abc123", "xyz098"})
// You can remove all entries at once.
lmt.RemoveHeader("X-Access-Token")
// Or remove specific ones.
lmt.RemoveHeaderEntries("X-Access-Token", []string{"limitless-token"})
// By the way, the setters are chainable. Example:
lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
SetMethods([]string{"GET", "POST"}).
SetBasicAuthUsers([]string{"sansa"}).
SetBasicAuthUsers([]string{"tyrion"})
```
2. Compose your own middleware by using `LimitByKeys()`.
3. Header entries and basic auth users can expire over time (to conserve memory).
```go
import "time"
lmt := tollbooth.NewLimiter(1, nil)
// Set a custom expiration TTL for token bucket.
lmt.SetTokenBucketExpirationTTL(time.Hour)
// Set a custom expiration TTL for basic auth users.
lmt.SetBasicAuthExpirationTTL(time.Hour)
// Set a custom expiration TTL for header entries.
lmt.SetHeaderEntryExpirationTTL(time.Hour)
```
4. Upon rejection, the following HTTP response headers are available to users:
* `X-Rate-Limit-Limit` The maximum request limit.
* `X-Rate-Limit-Duration` The rate-limiter duration.
* `X-Rate-Limit-Request-Forwarded-For` The rejected request `X-Forwarded-For`.
* `X-Rate-Limit-Request-Remote-Addr` The rejected request `RemoteAddr`.
5. Customize your own message or function when limit is reached.
```go
lmt := tollbooth.NewLimiter(1, nil)
// Set a custom message.
lmt.SetMessage("You have reached maximum request limit.")
// Set a custom content-type.
lmt.SetMessageContentType("text/plain; charset=utf-8")
// Set a custom function for rejection.
lmt.SetOnLimitReached(func(w http.ResponseWriter, r *http.Request) { fmt.Println("A request was rejected") })
```
6. Tollbooth does not require external storage since it uses an algorithm called [Token Bucket](http://en.wikipedia.org/wiki/Token_bucket) [(Go library: golang.org/x/time/rate)](//godoc.org/golang.org/x/time/rate).
## Other Web Frameworks
Sometimes, other frameworks require a little bit of shim to use Tollbooth. These shims below are contributed by the community, so I make no promises on how well they work. The one I am familiar with are: Chi, Gin, and Negroni.
* [Chi](https://github.com/didip/tollbooth_chi)
* [Echo](https://github.com/didip/tollbooth_echo)
* [FastHTTP](https://github.com/didip/tollbooth_fasthttp)
* [Gin](https://github.com/didip/tollbooth_gin)
* [GoRestful](https://github.com/didip/tollbooth_gorestful)
* [HTTPRouter](https://github.com/didip/tollbooth_httprouter)
* [Iris](https://github.com/didip/tollbooth_iris)
* [Negroni](https://github.com/didip/tollbooth_negroni)
## My other Go libraries
* [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite.
* [Gomet](https://github.com/didip/gomet): Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket.

View file

@ -1,15 +0,0 @@
// Package errors provide data structure for errors.
package errors
import "fmt"
// HTTPError is an error struct that returns both message and status code.
type HTTPError struct {
Message string
StatusCode int
}
// Error returns error message.
func (httperror *HTTPError) Error() string {
return fmt.Sprintf("%v: %v", httperror.StatusCode, httperror.Message)
}

View file

@ -1,55 +0,0 @@
// Package libstring provides various string related functions.
package libstring
import (
"net"
"net/http"
"strings"
)
// StringInSlice finds needle in a slice of strings.
func StringInSlice(sliceString []string, needle string) bool {
for _, b := range sliceString {
if b == needle {
return true
}
}
return false
}
// RemoteIP finds IP Address given http.Request struct.
func RemoteIP(ipLookups []string, forwardedForIndexFromBehind int, r *http.Request) string {
realIP := r.Header.Get("X-Real-IP")
forwardedFor := r.Header.Get("X-Forwarded-For")
for _, lookup := range ipLookups {
if lookup == "RemoteAddr" {
// 1. Cover the basic use cases for both ipv4 and ipv6
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
// 2. Upon error, just return the remote addr.
return r.RemoteAddr
}
return ip
}
if lookup == "X-Forwarded-For" && forwardedFor != "" {
// X-Forwarded-For is potentially a list of addresses separated with ","
parts := strings.Split(forwardedFor, ",")
for i, p := range parts {
parts[i] = strings.TrimSpace(p)
}
partIndex := len(parts) - 1 - forwardedForIndexFromBehind
if partIndex < 0 {
partIndex = 0
}
return parts[partIndex]
}
if lookup == "X-Real-IP" && realIP != "" {
return realIP
}
}
return ""
}

View file

@ -1,474 +0,0 @@
// Package limiter provides data structure to configure rate-limiter.
package limiter
import (
"net/http"
"sync"
"time"
gocache "github.com/patrickmn/go-cache"
"golang.org/x/time/rate"
)
// New is a constructor for Limiter.
func New(generalExpirableOptions *ExpirableOptions) *Limiter {
lmt := &Limiter{}
lmt.SetMessageContentType("text/plain; charset=utf-8").
SetMessage("You have reached maximum request limit.").
SetStatusCode(429).
SetOnLimitReached(nil).
SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
SetForwardedForIndexFromBehind(0).
SetHeaders(make(map[string][]string))
if generalExpirableOptions != nil {
lmt.generalExpirableOptions = generalExpirableOptions
} else {
lmt.generalExpirableOptions = &ExpirableOptions{}
}
// Default for ExpireJobInterval is every minute.
if lmt.generalExpirableOptions.ExpireJobInterval <= 0 {
lmt.generalExpirableOptions.ExpireJobInterval = time.Minute
}
// Default for DefaultExpirationTTL is 10 years.
if lmt.generalExpirableOptions.DefaultExpirationTTL <= 0 {
lmt.generalExpirableOptions.DefaultExpirationTTL = 87600 * time.Hour
}
lmt.tokenBuckets = gocache.New(
lmt.generalExpirableOptions.DefaultExpirationTTL,
lmt.generalExpirableOptions.ExpireJobInterval,
)
lmt.basicAuthUsers = gocache.New(
lmt.generalExpirableOptions.DefaultExpirationTTL,
lmt.generalExpirableOptions.ExpireJobInterval,
)
return lmt
}
// Limiter is a config struct to limit a particular request handler.
type Limiter struct {
// Maximum number of requests to limit per second.
max float64
// Limiter burst size
burst int
// HTTP message when limit is reached.
message string
// Content-Type for Message
messageContentType string
// HTTP status code when limit is reached.
statusCode int
// A function to call when a request is rejected.
onLimitReached func(w http.ResponseWriter, r *http.Request)
// List of places to look up IP address.
// Default is "RemoteAddr", "X-Forwarded-For", "X-Real-IP".
// You can rearrange the order as you like.
ipLookups []string
forwardedForIndex int
// List of HTTP Methods to limit (GET, POST, PUT, etc.).
// Empty means limit all methods.
methods []string
// Able to configure token bucket expirations.
generalExpirableOptions *ExpirableOptions
// List of basic auth usernames to limit.
basicAuthUsers *gocache.Cache
// Map of HTTP headers to limit.
// Empty means skip headers checking.
headers map[string]*gocache.Cache
// Map of limiters with TTL
tokenBuckets *gocache.Cache
tokenBucketExpirationTTL time.Duration
basicAuthExpirationTTL time.Duration
headerEntryExpirationTTL time.Duration
sync.RWMutex
}
// SetTokenBucketExpirationTTL is thread-safe way of setting custom token bucket expiration TTL.
func (l *Limiter) SetTokenBucketExpirationTTL(ttl time.Duration) *Limiter {
l.Lock()
l.tokenBucketExpirationTTL = ttl
l.Unlock()
return l
}
// GettokenBucketExpirationTTL is thread-safe way of getting custom token bucket expiration TTL.
func (l *Limiter) GetTokenBucketExpirationTTL() time.Duration {
l.RLock()
defer l.RUnlock()
return l.tokenBucketExpirationTTL
}
// SetBasicAuthExpirationTTL is thread-safe way of setting custom basic auth expiration TTL.
func (l *Limiter) SetBasicAuthExpirationTTL(ttl time.Duration) *Limiter {
l.Lock()
l.basicAuthExpirationTTL = ttl
l.Unlock()
return l
}
// GetBasicAuthExpirationTTL is thread-safe way of getting custom basic auth expiration TTL.
func (l *Limiter) GetBasicAuthExpirationTTL() time.Duration {
l.RLock()
defer l.RUnlock()
return l.basicAuthExpirationTTL
}
// SetHeaderEntryExpirationTTL is thread-safe way of setting custom basic auth expiration TTL.
func (l *Limiter) SetHeaderEntryExpirationTTL(ttl time.Duration) *Limiter {
l.Lock()
l.headerEntryExpirationTTL = ttl
l.Unlock()
return l
}
// GetHeaderEntryExpirationTTL is thread-safe way of getting custom basic auth expiration TTL.
func (l *Limiter) GetHeaderEntryExpirationTTL() time.Duration {
l.RLock()
defer l.RUnlock()
return l.headerEntryExpirationTTL
}
// SetMax is thread-safe way of setting maximum number of requests to limit per duration.
func (l *Limiter) SetMax(max float64) *Limiter {
l.Lock()
l.max = max
l.Unlock()
return l
}
// GetMax is thread-safe way of getting maximum number of requests to limit per duration.
func (l *Limiter) GetMax() float64 {
l.RLock()
defer l.RUnlock()
return l.max
}
// SetBurst is thread-safe way of setting maximum burst size.
func (l *Limiter) SetBurst(burst int) *Limiter {
l.Lock()
l.burst = burst
l.Unlock()
return l
}
// GetBurst is thread-safe way of setting maximum burst size.
func (l *Limiter) GetBurst() int {
l.RLock()
defer l.RUnlock()
return l.burst
}
// SetMessage is thread-safe way of setting HTTP message when limit is reached.
func (l *Limiter) SetMessage(msg string) *Limiter {
l.Lock()
l.message = msg
l.Unlock()
return l
}
// GetMessage is thread-safe way of getting HTTP message when limit is reached.
func (l *Limiter) GetMessage() string {
l.RLock()
defer l.RUnlock()
return l.message
}
// SetMessageContentType is thread-safe way of setting HTTP message Content-Type when limit is reached.
func (l *Limiter) SetMessageContentType(contentType string) *Limiter {
l.Lock()
l.messageContentType = contentType
l.Unlock()
return l
}
// GetMessageContentType is thread-safe way of getting HTTP message Content-Type when limit is reached.
func (l *Limiter) GetMessageContentType() string {
l.RLock()
defer l.RUnlock()
return l.messageContentType
}
// SetStatusCode is thread-safe way of setting HTTP status code when limit is reached.
func (l *Limiter) SetStatusCode(statusCode int) *Limiter {
l.Lock()
l.statusCode = statusCode
l.Unlock()
return l
}
// GetStatusCode is thread-safe way of getting HTTP status code when limit is reached.
func (l *Limiter) GetStatusCode() int {
l.RLock()
defer l.RUnlock()
return l.statusCode
}
// SetOnLimitReached is thread-safe way of setting after-rejection function when limit is reached.
func (l *Limiter) SetOnLimitReached(fn func(w http.ResponseWriter, r *http.Request)) *Limiter {
l.Lock()
l.onLimitReached = fn
l.Unlock()
return l
}
// ExecOnLimitReached is thread-safe way of executing after-rejection function when limit is reached.
func (l *Limiter) ExecOnLimitReached(w http.ResponseWriter, r *http.Request) {
l.RLock()
defer l.RUnlock()
fn := l.onLimitReached
if fn != nil {
fn(w, r)
}
}
// SetIPLookups is thread-safe way of setting list of places to look up IP address.
func (l *Limiter) SetIPLookups(ipLookups []string) *Limiter {
l.Lock()
l.ipLookups = ipLookups
l.Unlock()
return l
}
// GetIPLookups is thread-safe way of getting list of places to look up IP address.
func (l *Limiter) GetIPLookups() []string {
l.RLock()
defer l.RUnlock()
return l.ipLookups
}
// SetForwardedForIndexFromBehind is thread-safe way of setting which X-Forwarded-For index to choose.
func (l *Limiter) SetForwardedForIndexFromBehind(forwardedForIndex int) *Limiter {
l.Lock()
l.forwardedForIndex = forwardedForIndex
l.Unlock()
return l
}
// GetForwardedForIndexFromBehind is thread-safe way of getting which X-Forwarded-For index to choose.
func (l *Limiter) GetForwardedForIndexFromBehind() int {
l.RLock()
defer l.RUnlock()
return l.forwardedForIndex
}
// SetMethods is thread-safe way of setting list of HTTP Methods to limit (GET, POST, PUT, etc.).
func (l *Limiter) SetMethods(methods []string) *Limiter {
l.Lock()
l.methods = methods
l.Unlock()
return l
}
// GetMethods is thread-safe way of getting list of HTTP Methods to limit (GET, POST, PUT, etc.).
func (l *Limiter) GetMethods() []string {
l.RLock()
defer l.RUnlock()
return l.methods
}
// SetBasicAuthUsers is thread-safe way of setting list of basic auth usernames to limit.
func (l *Limiter) SetBasicAuthUsers(basicAuthUsers []string) *Limiter {
ttl := l.GetBasicAuthExpirationTTL()
if ttl <= 0 {
ttl = l.generalExpirableOptions.DefaultExpirationTTL
}
for _, basicAuthUser := range basicAuthUsers {
l.basicAuthUsers.Set(basicAuthUser, true, ttl)
}
return l
}
// GetBasicAuthUsers is thread-safe way of getting list of basic auth usernames to limit.
func (l *Limiter) GetBasicAuthUsers() []string {
asMap := l.basicAuthUsers.Items()
var basicAuthUsers []string
for basicAuthUser, _ := range asMap {
basicAuthUsers = append(basicAuthUsers, basicAuthUser)
}
return basicAuthUsers
}
// RemoveBasicAuthUsers is thread-safe way of removing basic auth usernames from existing list.
func (l *Limiter) RemoveBasicAuthUsers(basicAuthUsers []string) *Limiter {
for _, toBeRemoved := range basicAuthUsers {
l.basicAuthUsers.Delete(toBeRemoved)
}
return l
}
// SetHeaders is thread-safe way of setting map of HTTP headers to limit.
func (l *Limiter) SetHeaders(headers map[string][]string) *Limiter {
if l.headers == nil {
l.headers = make(map[string]*gocache.Cache)
}
for header, entries := range headers {
l.SetHeader(header, entries)
}
return l
}
// GetHeaders is thread-safe way of getting map of HTTP headers to limit.
func (l *Limiter) GetHeaders() map[string][]string {
results := make(map[string][]string)
l.RLock()
defer l.RUnlock()
for header, entriesAsGoCache := range l.headers {
entries := make([]string, 0)
for entry, _ := range entriesAsGoCache.Items() {
entries = append(entries, entry)
}
results[header] = entries
}
return results
}
// SetHeader is thread-safe way of setting entries of 1 HTTP header.
func (l *Limiter) SetHeader(header string, entries []string) *Limiter {
l.RLock()
existing, found := l.headers[header]
l.RUnlock()
ttl := l.GetHeaderEntryExpirationTTL()
if ttl <= 0 {
ttl = l.generalExpirableOptions.DefaultExpirationTTL
}
if !found {
existing = gocache.New(ttl, l.generalExpirableOptions.ExpireJobInterval)
}
for _, entry := range entries {
existing.Set(entry, true, ttl)
}
l.Lock()
l.headers[header] = existing
l.Unlock()
return l
}
// GetHeader is thread-safe way of getting entries of 1 HTTP header.
func (l *Limiter) GetHeader(header string) []string {
l.RLock()
entriesAsGoCache := l.headers[header]
l.RUnlock()
entriesAsMap := entriesAsGoCache.Items()
entries := make([]string, 0)
for entry, _ := range entriesAsMap {
entries = append(entries, entry)
}
return entries
}
// RemoveHeader is thread-safe way of removing entries of 1 HTTP header.
func (l *Limiter) RemoveHeader(header string) *Limiter {
ttl := l.GetHeaderEntryExpirationTTL()
if ttl <= 0 {
ttl = l.generalExpirableOptions.DefaultExpirationTTL
}
l.Lock()
l.headers[header] = gocache.New(ttl, l.generalExpirableOptions.ExpireJobInterval)
l.Unlock()
return l
}
// RemoveHeaderEntries is thread-safe way of adding new entries to 1 HTTP header rule.
func (l *Limiter) RemoveHeaderEntries(header string, entriesForRemoval []string) *Limiter {
l.RLock()
entries, found := l.headers[header]
l.RUnlock()
if !found {
return l
}
for _, toBeRemoved := range entriesForRemoval {
entries.Delete(toBeRemoved)
}
return l
}
func (l *Limiter) limitReachedWithTokenBucketTTL(key string, tokenBucketTTL time.Duration) bool {
lmtMax := l.GetMax()
lmtBurst := l.GetBurst()
l.Lock()
defer l.Unlock()
if _, found := l.tokenBuckets.Get(key); !found {
l.tokenBuckets.Set(
key,
rate.NewLimiter(rate.Limit(lmtMax), lmtBurst),
tokenBucketTTL,
)
}
expiringMap, found := l.tokenBuckets.Get(key)
if !found {
return false
}
return !expiringMap.(*rate.Limiter).Allow()
}
// LimitReached returns a bool indicating if the Bucket identified by key ran out of tokens.
func (l *Limiter) LimitReached(key string) bool {
ttl := l.GetTokenBucketExpirationTTL()
if ttl <= 0 {
ttl = l.generalExpirableOptions.DefaultExpirationTTL
}
return l.limitReachedWithTokenBucketTTL(key, ttl)
}

View file

@ -1,12 +0,0 @@
package limiter
import (
"time"
)
type ExpirableOptions struct {
DefaultExpirationTTL time.Duration
// How frequently expire job triggers
ExpireJobInterval time.Duration
}

View file

@ -1,180 +0,0 @@
// Package tollbooth provides rate-limiting logic to HTTP request handler.
package tollbooth
import (
"net/http"
"strings"
"fmt"
"github.com/didip/tollbooth/errors"
"github.com/didip/tollbooth/libstring"
"github.com/didip/tollbooth/limiter"
"math"
)
// setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
func setResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Rate-Limit-Limit", fmt.Sprintf("%.2f", lmt.GetMax()))
w.Header().Add("X-Rate-Limit-Duration", "1")
w.Header().Add("X-Rate-Limit-Request-Forwarded-For", r.Header.Get("X-Forwarded-For"))
w.Header().Add("X-Rate-Limit-Request-Remote-Addr", r.RemoteAddr)
}
// NewLimiter is a convenience function to limiter.New.
func NewLimiter(max float64, tbOptions *limiter.ExpirableOptions) *limiter.Limiter {
return limiter.New(tbOptions).SetMax(max).SetBurst(int(math.Max(1, max)))
}
// LimitByKeys keeps track number of request made by keys separated by pipe.
// It returns HTTPError when limit is exceeded.
func LimitByKeys(lmt *limiter.Limiter, keys []string) *errors.HTTPError {
if lmt.LimitReached(strings.Join(keys, "|")) {
return &errors.HTTPError{Message: lmt.GetMessage(), StatusCode: lmt.GetStatusCode()}
}
return nil
}
// BuildKeys generates a slice of keys to rate-limit by given limiter and request structs.
func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string {
remoteIP := libstring.RemoteIP(lmt.GetIPLookups(), lmt.GetForwardedForIndexFromBehind(), r)
path := r.URL.Path
sliceKeys := make([][]string, 0)
// Don't BuildKeys if remoteIP is blank.
if remoteIP == "" {
return sliceKeys
}
lmtMethods := lmt.GetMethods()
lmtHeaders := lmt.GetHeaders()
lmtBasicAuthUsers := lmt.GetBasicAuthUsers()
lmtHeadersIsSet := len(lmtHeaders) > 0
lmtBasicAuthUsersIsSet := len(lmtBasicAuthUsers) > 0
if lmtMethods != nil && lmtHeadersIsSet && lmtBasicAuthUsersIsSet {
// Limit by HTTP methods and HTTP headers+values and Basic Auth credentials.
if libstring.StringInSlice(lmtMethods, r.Method) {
for headerKey, headerValues := range lmtHeaders {
if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
// If header values are empty, rate-limit all request with headerKey.
username, _, ok := r.BasicAuth()
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, username})
}
} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
// If header values are not empty, rate-limit all request with headerKey and headerValues.
for _, headerValue := range headerValues {
username, _, ok := r.BasicAuth()
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username})
}
}
}
}
}
} else if lmtMethods != nil && lmtHeadersIsSet {
// Limit by HTTP methods and HTTP headers+values.
if libstring.StringInSlice(lmtMethods, r.Method) {
for headerKey, headerValues := range lmtHeaders {
if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
// If header values are empty, rate-limit all request with headerKey.
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey})
} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
// If header values are not empty, rate-limit all request with headerKey and headerValues.
for _, headerValue := range headerValues {
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue})
}
}
}
}
} else if lmtMethods != nil && lmtBasicAuthUsersIsSet {
// Limit by HTTP methods and Basic Auth credentials.
if libstring.StringInSlice(lmtMethods, r.Method) {
username, _, ok := r.BasicAuth()
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, username})
}
}
} else if lmtMethods != nil {
// Limit by HTTP methods.
if libstring.StringInSlice(lmtMethods, r.Method) {
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method})
}
} else if lmtHeadersIsSet {
// Limit by HTTP headers+values.
for headerKey, headerValues := range lmtHeaders {
if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
// If header values are empty, rate-limit all request with headerKey.
sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey})
} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
// If header values are not empty, rate-limit all request with headerKey and headerValues.
for _, headerValue := range headerValues {
sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue})
}
}
}
} else if lmtBasicAuthUsersIsSet {
// Limit by Basic Auth credentials.
username, _, ok := r.BasicAuth()
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
sliceKeys = append(sliceKeys, []string{remoteIP, path, username})
}
} else {
// Default: Limit by remoteIP and path.
sliceKeys = append(sliceKeys, []string{remoteIP, path})
}
return sliceKeys
}
// LimitByRequest builds keys based on http.Request struct,
// loops through all the keys, and check if any one of them returns HTTPError.
func LimitByRequest(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
setResponseHeaders(lmt, w, r)
sliceKeys := BuildKeys(lmt, r)
// Loop sliceKeys and check if one of them has error.
for _, keys := range sliceKeys {
httpError := LimitByKeys(lmt, keys)
if httpError != nil {
return httpError
}
}
return nil
}
// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
func LimitHandler(lmt *limiter.Limiter, next http.Handler) http.Handler {
middle := func(w http.ResponseWriter, r *http.Request) {
httpError := LimitByRequest(lmt, w, r)
if httpError != nil {
lmt.ExecOnLimitReached(w, r)
w.Header().Add("Content-Type", lmt.GetMessageContentType())
w.WriteHeader(httpError.StatusCode)
w.Write([]byte(httpError.Message))
return
}
// There's no rate-limit error, serve the next handler.
next.ServeHTTP(w, r)
}
return http.HandlerFunc(middle)
}
// LimitFuncHandler is a middleware that performs rate-limiting given request handler function.
func LimitFuncHandler(lmt *limiter.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
return LimitHandler(lmt, http.HandlerFunc(nextFunc))
}

View file

@ -1,3 +0,0 @@
language: go
go:
- 1.5

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Florian Pigorsch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,57 +0,0 @@
[![GoDoc](https://godoc.org/github.com/flopp/go-coordsparser?status.svg)](https://godoc.org/github.com/flopp/go-coordsparser)
[![Build Status](https://travis-ci.org/flopp/go-coordsparser.svg)](https://travis-ci.org/flopp/go-coordsparser)
[![Go Report Card](https://goreportcard.com/badge/flopp/go-coordsparser)](https://goreportcard.com/report/flopp/go-coordsparser)
[![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/flopp/go-coordsparser)
# go-coordsparser
A library for parsing (geographic) coordinates in go (golang)
# What?
go-coordsparser allows you to parse lat/lng coordinates from strings in various popular formats. Currently supported formats are:
- **D** (decimal degrees), e.g. `40.76, -73.984`
- **HD** (hemisphere prefix, decimal degrees), e.g. `N 40.76 W 73.984`
- **HDM** (hemisphere prefix, integral degrees, decimal minutes), e.g. `N 40 45.600 W 73 59.040`
- **HDMS** (hemisphere prefix, integral degrees, integral minutes, decimal seconds), e.g. `N 40 45 36.0 W 73 59 02.4`
# How?
### Installing
Installing the library is as easy as
```bash
$ go get github.com/flopp/go-coordsparser
```
The package can then be used through an
```go
import "github.com/flopp/go-coordsparser"
```
### Using
go-coordsparser provides several functions for parsing coordinate strings: a general parsing function `coordsparser.Parse`, which accepts all supported formats, as well as specialized functions `coordsparser.ParseD`, `coordsparser.ParseHD`, `coordsparser.ParseHDM`, `coordsparser.ParseHDMS` for the corresponding coordinate formats.
Each function takes a single string as a parameter and returns an idiomatic `lat, lng, error` triple, where `lat` and `lng` are decimal degrees (`float64`) with -90 ≤ `lat` ≤ 90 and -180 ≤ `lng` ≤ 180.
```go
// parse any format
s1 := "..."
lat1, lng1, err := coordsparser.Parse(s1)
if err != nil {
fmt.Errorf("Cannot parse coordinates string:", s1)
}
// parse specific format, e.g. HDM
s2 := "..."
lat2, lng2, err = coordsparser.ParseHDM(s2)
if err != nil {
fmt.Errorf("Cannot parse coordinates string:", s2)
}
```
# License
Copyright 2016 Florian Pigorsch. All rights reserved.
Use of this source code is governed by a MIT-style license that can be found in the LICENSE file.

View file

@ -1,172 +0,0 @@
// Copyright 2016 Florian Pigorsch. All rights reserved.
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package coordsparser is a library for parsing (geographic) coordinates in various string formats
package coordsparser
import (
"fmt"
"regexp"
"strconv"
)
// Parse parses a coordinate string and returns a lat/lng pair or an error
func Parse(s string) (float64, float64, error) {
lat, lng, err := ParseD(s)
if err == nil {
return lat, lng, nil
}
lat, lng, err = ParseHD(s)
if err == nil {
return lat, lng, nil
}
lat, lng, err = ParseHDM(s)
if err == nil {
return lat, lng, nil
}
lat, lng, err = ParseHDMS(s)
if err == nil {
return lat, lng, nil
}
return 0, 0, fmt.Errorf("Cannot parse coordinates: %s", s)
}
// ParseD parses a coordinate string of the form "D.DDDD D.DDDD" and returns a lat/lng pair or an error
func ParseD(s string) (float64, float64, error) {
re := regexp.MustCompile(`^\s*([+-]?[\d\.]+)\s*(,|;|:|\s)\s*([+-]?[\d\.]+)\s*$`)
matches := re.FindStringSubmatch(s)
if matches == nil {
return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s)
}
lat, err := strconv.ParseFloat(matches[1], 64)
if err != nil || lat < -90 || lat > 90 {
return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s)
}
lng, err := strconv.ParseFloat(matches[3], 64)
if err != nil || lng < -180 || lng > 180 {
return 0, 0, fmt.Errorf("Cannot parse 'D' string: %s", s)
}
return lat, lng, nil
}
// ParseHD parses a coordinate string of the form "H D.DDDD H D.DDDD" and returns a lat/lng pair or an error
func ParseHD(s string) (float64, float64, error) {
re := regexp.MustCompile(`^\s*([NnSs])\s*([\d\.]+)\s+([EeWw])\s*([\d\.]+)\s*$`)
matches := re.FindStringSubmatch(s)
if matches == nil {
return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s)
}
lat, err := strconv.ParseFloat(matches[2], 64)
if err != nil || lat > 90 {
return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s)
}
if matches[1] == "S" || matches[1] == "s" {
lat = -lat
}
lng, err := strconv.ParseFloat(matches[4], 64)
if err != nil || lng > 180 {
return 0, 0, fmt.Errorf("Cannot parse 'HD' string: %s", s)
}
if matches[3] == "W" || matches[3] == "w" {
lng = -lng
}
return lat, lng, nil
}
// ParseHDM parses a coordinate string of the form "H D M.MMM H D M.MMM" and returns a lat/lng pair or an error
func ParseHDM(s string) (float64, float64, error) {
re := regexp.MustCompile(`^\s*([NnSs])\s*([\d]+)\s+([\d.]+)\s+([EeWw])\s*([\d]+)\s+([\d.]+)\s*$`)
matches := re.FindStringSubmatch(s)
if matches == nil {
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
}
latDeg, err := strconv.ParseFloat(matches[2], 64)
if err != nil || latDeg > 90 {
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
}
latMin, err := strconv.ParseFloat(matches[3], 64)
if err != nil || latMin >= 60 {
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
}
lat := latDeg + latMin/60.0
if matches[1] == "S" || matches[1] == "s" {
lat = -lat
}
lngDeg, err := strconv.ParseFloat(matches[5], 64)
if err != nil || lngDeg > 180 {
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
}
lngMin, err := strconv.ParseFloat(matches[6], 64)
if err != nil || lngMin >= 60 {
return 0, 0, fmt.Errorf("Cannot parse 'HDM' string: %s", s)
}
lng := lngDeg + lngMin/60.0
if matches[4] == "W" || matches[4] == "w" {
lng = -lng
}
return lat, lng, nil
}
// ParseHDMS parses a coordinate string of the form "H D M S.SSS H D M S.SSS" and returns a lat/lng pair or an error
func ParseHDMS(s string) (float64, float64, error) {
re := regexp.MustCompile(`^\s*([NnSs])\s*([\d]+)\s+([\d]+)\s+([\d.]+)\s+([EeWw])\s*([\d]+)\s+([\d]+)\s+([\d.]+)\s*$`)
matches := re.FindStringSubmatch(s)
if matches == nil {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
latDeg, err := strconv.ParseFloat(matches[2], 64)
if err != nil || latDeg > 90 {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
latMin, err := strconv.ParseFloat(matches[3], 64)
if err != nil || latMin >= 60 {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
latSec, err := strconv.ParseFloat(matches[4], 64)
if err != nil || latSec >= 60 {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
lat := latDeg + latMin/60.0 + latSec/3600.0
if matches[1] == "S" || matches[1] == "s" {
lat = -lat
}
lngDeg, err := strconv.ParseFloat(matches[6], 64)
if err != nil || lngDeg > 180 {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
lngMin, err := strconv.ParseFloat(matches[7], 64)
if err != nil || lngMin >= 60 {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
lngSec, err := strconv.ParseFloat(matches[8], 64)
if err != nil || lngSec >= 60 {
return 0, 0, fmt.Errorf("Cannot parse 'HDMS' string: %s", s)
}
lng := lngDeg + lngMin/60.0 + lngSec/3600.0
if matches[5] == "W" || matches[5] == "w" {
lng = -lng
}
return lat, lng, nil
}

View file

@ -1,2 +0,0 @@
*.png

View file

@ -1,19 +0,0 @@
Copyright (C) 2016 Michael Fogleman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,220 +0,0 @@
# Go Graphics
`gg` is a library for rendering 2D graphics in pure Go.
![Stars](http://i.imgur.com/CylQIJt.png)
## Installation
go get github.com/fogleman/gg
## GoDoc
https://godoc.org/github.com/fogleman/gg
## Hello, Circle!
Look how easy!
```go
package main
import "github.com/fogleman/gg"
func main() {
dc := gg.NewContext(1000, 1000)
dc.DrawCircle(500, 500, 400)
dc.SetRGB(0, 0, 0)
dc.Fill()
dc.SavePNG("out.png")
}
```
## Examples
There are [lots of examples](https://github.com/fogleman/gg/tree/master/examples) included. They're mostly for testing the code, but they're good for learning, too.
![Examples](http://i.imgur.com/tMFoyzu.png)
## Creating Contexts
There are a few ways of creating a context.
```go
NewContext(width, height int) *Context
NewContextForImage(im image.Image) *Context
NewContextForRGBA(im *image.RGBA) *Context
```
## Drawing Functions
Ever used a graphics library that didn't have functions for drawing rectangles
or circles? What a pain!
```go
DrawPoint(x, y, r float64)
DrawLine(x1, y1, x2, y2 float64)
DrawRectangle(x, y, w, h float64)
DrawRoundedRectangle(x, y, w, h, r float64)
DrawCircle(x, y, r float64)
DrawArc(x, y, r, angle1, angle2 float64)
DrawEllipse(x, y, rx, ry float64)
DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64)
DrawRegularPolygon(n int, x, y, r, rotation float64)
DrawImage(im image.Image, x, y int)
DrawImageAnchored(im image.Image, x, y int, ax, ay float64)
SetPixel(x, y int)
MoveTo(x, y float64)
LineTo(x, y float64)
QuadraticTo(x1, y1, x2, y2 float64)
CubicTo(x1, y1, x2, y2, x3, y3 float64)
ClosePath()
ClearPath()
NewSubPath()
Clear()
Stroke()
Fill()
StrokePreserve()
FillPreserve()
```
It is often desired to center an image at a point. Use `DrawImageAnchored` with `ax` and `ay` set to 0.5 to do this. Use 0 to left or top align. Use 1 to right or bottom align. `DrawStringAnchored` does the same for text, so you don't need to call `MeasureString` yourself.
## Text Functions
It will even do word wrap for you!
```go
DrawString(s string, x, y float64)
DrawStringAnchored(s string, x, y, ax, ay float64)
DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align)
MeasureString(s string) (w, h float64)
WordWrap(s string, w float64) []string
SetFontFace(fontFace font.Face)
LoadFontFace(path string, points float64) error
```
## Color Functions
Colors can be set in several different ways for your convenience.
```go
SetRGB(r, g, b float64)
SetRGBA(r, g, b, a float64)
SetRGB255(r, g, b int)
SetRGBA255(r, g, b, a int)
SetColor(c color.Color)
SetHexColor(x string)
```
## Stroke & Fill Options
```go
SetLineWidth(lineWidth float64)
SetLineCap(lineCap LineCap)
SetLineJoin(lineJoin LineJoin)
SetDash(dashes ...float64)
SetFillRule(fillRule FillRule)
```
## Gradients & Patterns
`gg` supports linear and radial gradients and surface patterns. You can also implement your own patterns.
```go
SetFillStyle(pattern Pattern)
SetStrokeStyle(pattern Pattern)
NewSolidPattern(color color.Color)
NewLinearGradient(x0, y0, x1, y1 float64)
NewRadialGradient(x0, y0, r0, x1, y1, r1 float64)
NewSurfacePattern(im image.Image, op RepeatOp)
```
## Transformation Functions
```go
Identity()
Translate(x, y float64)
Scale(x, y float64)
Rotate(angle float64)
Shear(x, y float64)
ScaleAbout(sx, sy, x, y float64)
RotateAbout(angle, x, y float64)
ShearAbout(sx, sy, x, y float64)
TransformPoint(x, y float64) (tx, ty float64)
InvertY()
```
It is often desired to rotate or scale about a point that is not the origin. The functions `RotateAbout`, `ScaleAbout`, `ShearAbout` are provided as a convenience.
`InvertY` is provided in case Y should increase from bottom to top vs. the default top to bottom.
Note: transforms do not currently affect `DrawImage` or `DrawString`.
## Stack Functions
Save and restore the state of the context. These can be nested.
```go
Push()
Pop()
```
## Clipping Functions
Use clipping regions to restrict drawing operations to an area that you
defined using paths.
```go
Clip()
ClipPreserve()
ResetClip()
```
## Helper Functions
Sometimes you just don't want to write these yourself.
```go
Radians(degrees float64) float64
Degrees(radians float64) float64
LoadImage(path string) (image.Image, error)
LoadPNG(path string) (image.Image, error)
SavePNG(path string, im image.Image) error
```
![Separator](http://i.imgur.com/fsUvnPB.png)
## How Do it Do?
`gg` is mostly a wrapper around `github.com/golang/freetype/raster`. The goal
is to provide some more functionality and a nicer API that will suffice for
most use cases.
## Another Example
See the output of this example below.
```go
package main
import "github.com/fogleman/gg"
func main() {
const S = 1024
dc := gg.NewContext(S, S)
dc.SetRGBA(0, 0, 0, 0.1)
for i := 0; i < 360; i += 15 {
dc.Push()
dc.RotateAbout(gg.Radians(float64(i)), S/2, S/2)
dc.DrawEllipse(S/2, S/2, S*7/16, S/8)
dc.Fill()
dc.Pop()
}
dc.SavePNG("out.png")
}
```
![Ellipses](http://i.imgur.com/J9CBZef.png)

View file

@ -1,59 +0,0 @@
package gg
import "math"
func quadratic(x0, y0, x1, y1, x2, y2, t float64) (x, y float64) {
u := 1 - t
a := u * u
b := 2 * u * t
c := t * t
x = a*x0 + b*x1 + c*x2
y = a*y0 + b*y1 + c*y2
return
}
func QuadraticBezier(x0, y0, x1, y1, x2, y2 float64) []Point {
l := (math.Hypot(x1-x0, y1-y0) +
math.Hypot(x2-x1, y2-y1))
n := int(l + 0.5)
if n < 4 {
n = 4
}
d := float64(n) - 1
result := make([]Point, n)
for i := 0; i < n; i++ {
t := float64(i) / d
x, y := quadratic(x0, y0, x1, y1, x2, y2, t)
result[i] = Point{x, y}
}
return result
}
func cubic(x0, y0, x1, y1, x2, y2, x3, y3, t float64) (x, y float64) {
u := 1 - t
a := u * u * u
b := 3 * u * u * t
c := 3 * u * t * t
d := t * t * t
x = a*x0 + b*x1 + c*x2 + d*x3
y = a*y0 + b*y1 + c*y2 + d*y3
return
}
func CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3 float64) []Point {
l := (math.Hypot(x1-x0, y1-y0) +
math.Hypot(x2-x1, y2-y1) +
math.Hypot(x3-x2, y3-y2))
n := int(l + 0.5)
if n < 4 {
n = 4
}
d := float64(n) - 1
result := make([]Point, n)
for i := 0; i < n; i++ {
t := float64(i) / d
x, y := cubic(x0, y0, x1, y1, x2, y2, x3, y3, t)
result[i] = Point{x, y}
}
return result
}

View file

@ -1,768 +0,0 @@
// Package gg provides a simple API for rendering 2D graphics in pure Go.
package gg
import (
"image"
"image/color"
"image/draw"
"image/png"
"io"
"math"
"github.com/golang/freetype/raster"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
)
type LineCap int
const (
LineCapRound LineCap = iota
LineCapButt
LineCapSquare
)
type LineJoin int
const (
LineJoinRound LineJoin = iota
LineJoinBevel
)
type FillRule int
const (
FillRuleWinding FillRule = iota
FillRuleEvenOdd
)
type Align int
const (
AlignLeft Align = iota
AlignCenter
AlignRight
)
var (
defaultFillStyle = NewSolidPattern(color.White)
defaultStrokeStyle = NewSolidPattern(color.Black)
)
type Context struct {
width int
height int
im *image.RGBA
mask *image.Alpha
color color.Color
fillPattern Pattern
strokePattern Pattern
strokePath raster.Path
fillPath raster.Path
start Point
current Point
hasCurrent bool
dashes []float64
lineWidth float64
lineCap LineCap
lineJoin LineJoin
fillRule FillRule
fontFace font.Face
fontHeight float64
matrix Matrix
stack []*Context
}
// NewContext creates a new image.RGBA with the specified width and height
// and prepares a context for rendering onto that image.
func NewContext(width, height int) *Context {
return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height)))
}
// NewContextForImage copies the specified image into a new image.RGBA
// and prepares a context for rendering onto that image.
func NewContextForImage(im image.Image) *Context {
return NewContextForRGBA(imageToRGBA(im))
}
// NewContextForRGBA prepares a context for rendering onto the specified image.
// No copy is made.
func NewContextForRGBA(im *image.RGBA) *Context {
return &Context{
width: im.Bounds().Size().X,
height: im.Bounds().Size().Y,
im: im,
color: color.Transparent,
fillPattern: defaultFillStyle,
strokePattern: defaultStrokeStyle,
lineWidth: 1,
fillRule: FillRuleWinding,
fontFace: basicfont.Face7x13,
fontHeight: 13,
matrix: Identity(),
}
}
// Image returns the image that has been drawn by this context.
func (dc *Context) Image() image.Image {
return dc.im
}
// Width returns the width of the image in pixels.
func (dc *Context) Width() int {
return dc.width
}
// Height returns the height of the image in pixels.
func (dc *Context) Height() int {
return dc.height
}
// SavePNG encodes the image as a PNG and writes it to disk.
func (dc *Context) SavePNG(path string) error {
return SavePNG(path, dc.im)
}
// EncodePNG encodes the image as a PNG and writes it to the provided io.Writer.
func (dc *Context) EncodePNG(w io.Writer) error {
return png.Encode(w, dc.im)
}
// SetDash sets the current dash pattern to use. Call with zero arguments to
// disable dashes. The values specify the lengths of each dash, with
// alternating on and off lengths.
func (dc *Context) SetDash(dashes ...float64) {
dc.dashes = dashes
}
func (dc *Context) SetLineWidth(lineWidth float64) {
dc.lineWidth = lineWidth
}
func (dc *Context) SetLineCap(lineCap LineCap) {
dc.lineCap = lineCap
}
func (dc *Context) SetLineCapRound() {
dc.lineCap = LineCapRound
}
func (dc *Context) SetLineCapButt() {
dc.lineCap = LineCapButt
}
func (dc *Context) SetLineCapSquare() {
dc.lineCap = LineCapSquare
}
func (dc *Context) SetLineJoin(lineJoin LineJoin) {
dc.lineJoin = lineJoin
}
func (dc *Context) SetLineJoinRound() {
dc.lineJoin = LineJoinRound
}
func (dc *Context) SetLineJoinBevel() {
dc.lineJoin = LineJoinBevel
}
func (dc *Context) SetFillRule(fillRule FillRule) {
dc.fillRule = fillRule
}
func (dc *Context) SetFillRuleWinding() {
dc.fillRule = FillRuleWinding
}
func (dc *Context) SetFillRuleEvenOdd() {
dc.fillRule = FillRuleEvenOdd
}
// Color Setters
func (dc *Context) setFillAndStrokeColor(c color.Color) {
dc.color = c
dc.fillPattern = NewSolidPattern(c)
dc.strokePattern = NewSolidPattern(c)
}
// SetFillStyle sets current fill style
func (dc *Context) SetFillStyle(pattern Pattern) {
// if pattern is SolidPattern, also change dc.color(for dc.Clear, dc.drawString)
if fillStyle, ok := pattern.(*solidPattern); ok {
dc.color = fillStyle.color
}
dc.fillPattern = pattern
}
// SetStrokeStyle sets current stroke style
func (dc *Context) SetStrokeStyle(pattern Pattern) {
dc.strokePattern = pattern
}
// SetColor sets the current color(for both fill and stroke).
func (dc *Context) SetColor(c color.Color) {
dc.setFillAndStrokeColor(c)
}
// SetHexColor sets the current color using a hex string. The leading pound
// sign (#) is optional. Both 3- and 6-digit variations are supported. 8 digits
// may be provided to set the alpha value as well.
func (dc *Context) SetHexColor(x string) {
r, g, b, a := parseHexColor(x)
dc.SetRGBA255(r, g, b, a)
}
// SetRGBA255 sets the current color. r, g, b, a values should be between 0 and
// 255, inclusive.
func (dc *Context) SetRGBA255(r, g, b, a int) {
dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
dc.setFillAndStrokeColor(dc.color)
}
// SetRGB255 sets the current color. r, g, b values should be between 0 and 255,
// inclusive. Alpha will be set to 255 (fully opaque).
func (dc *Context) SetRGB255(r, g, b int) {
dc.SetRGBA255(r, g, b, 255)
}
// SetRGBA sets the current color. r, g, b, a values should be between 0 and 1,
// inclusive.
func (dc *Context) SetRGBA(r, g, b, a float64) {
dc.color = color.NRGBA{
uint8(r * 255),
uint8(g * 255),
uint8(b * 255),
uint8(a * 255),
}
dc.setFillAndStrokeColor(dc.color)
}
// SetRGB sets the current color. r, g, b values should be between 0 and 1,
// inclusive. Alpha will be set to 1 (fully opaque).
func (dc *Context) SetRGB(r, g, b float64) {
dc.SetRGBA(r, g, b, 1)
}
// Path Manipulation
// MoveTo starts a new subpath within the current path starting at the
// specified point.
func (dc *Context) MoveTo(x, y float64) {
if dc.hasCurrent {
dc.fillPath.Add1(dc.start.Fixed())
}
x, y = dc.TransformPoint(x, y)
p := Point{x, y}
dc.strokePath.Start(p.Fixed())
dc.fillPath.Start(p.Fixed())
dc.start = p
dc.current = p
dc.hasCurrent = true
}
// LineTo adds a line segment to the current path starting at the current
// point. If there is no current point, it is equivalent to MoveTo(x, y)
func (dc *Context) LineTo(x, y float64) {
if !dc.hasCurrent {
dc.MoveTo(x, y)
} else {
x, y = dc.TransformPoint(x, y)
p := Point{x, y}
dc.strokePath.Add1(p.Fixed())
dc.fillPath.Add1(p.Fixed())
dc.current = p
}
}
// QuadraticTo adds a quadratic bezier curve to the current path starting at
// the current point. If there is no current point, it first performs
// MoveTo(x1, y1)
func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) {
if !dc.hasCurrent {
dc.MoveTo(x1, y1)
}
x1, y1 = dc.TransformPoint(x1, y1)
x2, y2 = dc.TransformPoint(x2, y2)
p1 := Point{x1, y1}
p2 := Point{x2, y2}
dc.strokePath.Add2(p1.Fixed(), p2.Fixed())
dc.fillPath.Add2(p1.Fixed(), p2.Fixed())
dc.current = p2
}
// CubicTo adds a cubic bezier curve to the current path starting at the
// current point. If there is no current point, it first performs
// MoveTo(x1, y1). Because freetype/raster does not support cubic beziers,
// this is emulated with many small line segments.
func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) {
if !dc.hasCurrent {
dc.MoveTo(x1, y1)
}
x0, y0 := dc.current.X, dc.current.Y
x1, y1 = dc.TransformPoint(x1, y1)
x2, y2 = dc.TransformPoint(x2, y2)
x3, y3 = dc.TransformPoint(x3, y3)
points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3)
previous := dc.current.Fixed()
for _, p := range points[1:] {
f := p.Fixed()
if f == previous {
// TODO: this fixes some rendering issues but not all
continue
}
previous = f
dc.strokePath.Add1(f)
dc.fillPath.Add1(f)
dc.current = p
}
}
// ClosePath adds a line segment from the current point to the beginning
// of the current subpath. If there is no current point, this is a no-op.
func (dc *Context) ClosePath() {
if dc.hasCurrent {
dc.strokePath.Add1(dc.start.Fixed())
dc.fillPath.Add1(dc.start.Fixed())
dc.current = dc.start
}
}
// ClearPath clears the current path. There is no current point after this
// operation.
func (dc *Context) ClearPath() {
dc.strokePath.Clear()
dc.fillPath.Clear()
dc.hasCurrent = false
}
// NewSubPath starts a new subpath within the current path. There is no current
// point after this operation.
func (dc *Context) NewSubPath() {
if dc.hasCurrent {
dc.fillPath.Add1(dc.start.Fixed())
}
dc.hasCurrent = false
}
// Path Drawing
func (dc *Context) capper() raster.Capper {
switch dc.lineCap {
case LineCapButt:
return raster.ButtCapper
case LineCapRound:
return raster.RoundCapper
case LineCapSquare:
return raster.SquareCapper
}
return nil
}
func (dc *Context) joiner() raster.Joiner {
switch dc.lineJoin {
case LineJoinBevel:
return raster.BevelJoiner
case LineJoinRound:
return raster.RoundJoiner
}
return nil
}
func (dc *Context) stroke(painter raster.Painter) {
path := dc.strokePath
if len(dc.dashes) > 0 {
path = dashed(path, dc.dashes)
} else {
// TODO: this is a temporary workaround to remove tiny segments
// that result in rendering issues
path = rasterPath(flattenPath(path))
}
r := raster.NewRasterizer(dc.width, dc.height)
r.UseNonZeroWinding = true
r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner())
r.Rasterize(painter)
}
func (dc *Context) fill(painter raster.Painter) {
path := dc.fillPath
if dc.hasCurrent {
path = make(raster.Path, len(dc.fillPath))
copy(path, dc.fillPath)
path.Add1(dc.start.Fixed())
}
r := raster.NewRasterizer(dc.width, dc.height)
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
r.AddPath(path)
r.Rasterize(painter)
}
// StrokePreserve strokes the current path with the current color, line width,
// line cap, line join and dash settings. The path is preserved after this
// operation.
func (dc *Context) StrokePreserve() {
painter := newPatternPainter(dc.im, dc.mask, dc.strokePattern)
dc.stroke(painter)
}
// Stroke strokes the current path with the current color, line width,
// line cap, line join and dash settings. The path is cleared after this
// operation.
func (dc *Context) Stroke() {
dc.StrokePreserve()
dc.ClearPath()
}
// FillPreserve fills the current path with the current color. Open subpaths
// are implicity closed. The path is preserved after this operation.
func (dc *Context) FillPreserve() {
painter := newPatternPainter(dc.im, dc.mask, dc.fillPattern)
dc.fill(painter)
}
// Fill fills the current path with the current color. Open subpaths
// are implicity closed. The path is cleared after this operation.
func (dc *Context) Fill() {
dc.FillPreserve()
dc.ClearPath()
}
// ClipPreserve updates the clipping region by intersecting the current
// clipping region with the current path as it would be filled by dc.Fill().
// The path is preserved after this operation.
func (dc *Context) ClipPreserve() {
clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
painter := raster.NewAlphaOverPainter(clip)
dc.fill(painter)
if dc.mask == nil {
dc.mask = clip
} else {
mask := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
draw.DrawMask(mask, mask.Bounds(), clip, image.ZP, dc.mask, image.ZP, draw.Over)
dc.mask = mask
}
}
// Clip updates the clipping region by intersecting the current
// clipping region with the current path as it would be filled by dc.Fill().
// The path is cleared after this operation.
func (dc *Context) Clip() {
dc.ClipPreserve()
dc.ClearPath()
}
// ResetClip clears the clipping region.
func (dc *Context) ResetClip() {
dc.mask = nil
}
// Convenient Drawing Functions
// Clear fills the entire image with the current color.
func (dc *Context) Clear() {
src := image.NewUniform(dc.color)
draw.Draw(dc.im, dc.im.Bounds(), src, image.ZP, draw.Src)
}
// SetPixel sets the color of the specified pixel using the current color.
func (dc *Context) SetPixel(x, y int) {
dc.im.Set(x, y, dc.color)
}
// DrawPoint is like DrawCircle but ensures that a circle of the specified
// size is drawn regardless of the current transformation matrix. The position
// is still transformed, but not the shape of the point.
func (dc *Context) DrawPoint(x, y, r float64) {
dc.Push()
tx, ty := dc.TransformPoint(x, y)
dc.Identity()
dc.DrawCircle(tx, ty, r)
dc.Pop()
}
func (dc *Context) DrawLine(x1, y1, x2, y2 float64) {
dc.MoveTo(x1, y1)
dc.LineTo(x2, y2)
}
func (dc *Context) DrawRectangle(x, y, w, h float64) {
dc.NewSubPath()
dc.MoveTo(x, y)
dc.LineTo(x+w, y)
dc.LineTo(x+w, y+h)
dc.LineTo(x, y+h)
dc.ClosePath()
}
func (dc *Context) DrawRoundedRectangle(x, y, w, h, r float64) {
x0, x1, x2, x3 := x, x+r, x+w-r, x+w
y0, y1, y2, y3 := y, y+r, y+h-r, y+h
dc.NewSubPath()
dc.MoveTo(x1, y0)
dc.LineTo(x2, y0)
dc.DrawArc(x2, y1, r, Radians(270), Radians(360))
dc.LineTo(x3, y2)
dc.DrawArc(x2, y2, r, Radians(0), Radians(90))
dc.LineTo(x1, y3)
dc.DrawArc(x1, y2, r, Radians(90), Radians(180))
dc.LineTo(x0, y1)
dc.DrawArc(x1, y1, r, Radians(180), Radians(270))
dc.ClosePath()
}
func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) {
const n = 16
for i := 0; i < n; i++ {
p1 := float64(i+0) / n
p2 := float64(i+1) / n
a1 := angle1 + (angle2-angle1)*p1
a2 := angle1 + (angle2-angle1)*p2
x0 := x + rx*math.Cos(a1)
y0 := y + ry*math.Sin(a1)
x1 := x + rx*math.Cos(a1+(a2-a1)/2)
y1 := y + ry*math.Sin(a1+(a2-a1)/2)
x2 := x + rx*math.Cos(a2)
y2 := y + ry*math.Sin(a2)
cx := 2*x1 - x0/2 - x2/2
cy := 2*y1 - y0/2 - y2/2
if i == 0 && !dc.hasCurrent {
dc.MoveTo(x0, y0)
}
dc.QuadraticTo(cx, cy, x2, y2)
}
}
func (dc *Context) DrawEllipse(x, y, rx, ry float64) {
dc.NewSubPath()
dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi)
dc.ClosePath()
}
func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) {
dc.DrawEllipticalArc(x, y, r, r, angle1, angle2)
}
func (dc *Context) DrawCircle(x, y, r float64) {
dc.NewSubPath()
dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi)
dc.ClosePath()
}
func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) {
angle := 2 * math.Pi / float64(n)
rotation -= math.Pi / 2
if n%2 == 0 {
rotation += angle / 2
}
dc.NewSubPath()
for i := 0; i < n; i++ {
a := rotation + angle*float64(i)
dc.LineTo(x+r*math.Cos(a), y+r*math.Sin(a))
}
dc.ClosePath()
}
// DrawImage draws the specified image at the specified point.
// Currently, rotation and scaling transforms are not supported.
func (dc *Context) DrawImage(im image.Image, x, y int) {
dc.DrawImageAnchored(im, x, y, 0, 0)
}
// DrawImageAnchored draws the specified image at the specified anchor point.
// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
// image. Use ax=0.5, ay=0.5 to center the image at the specified point.
func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) {
s := im.Bounds().Size()
x -= int(ax * float64(s.X))
y -= int(ay * float64(s.Y))
p := image.Pt(x, y)
r := image.Rectangle{p, p.Add(s)}
if dc.mask == nil {
draw.Draw(dc.im, r, im, image.ZP, draw.Over)
} else {
draw.DrawMask(dc.im, r, im, image.ZP, dc.mask, p, draw.Over)
}
}
// Text Functions
func (dc *Context) SetFontFace(fontFace font.Face) {
dc.fontFace = fontFace
dc.fontHeight = float64(fontFace.Metrics().Height) / 64
}
func (dc *Context) LoadFontFace(path string, points float64) error {
face, err := LoadFontFace(path, points)
if err == nil {
dc.fontFace = face
dc.fontHeight = points * 72 / 96
}
return err
}
func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) {
d := &font.Drawer{
Dst: im,
Src: image.NewUniform(dc.color),
Face: dc.fontFace,
Dot: fixp(x, y),
}
d.DrawString(s)
}
// DrawString draws the specified text at the specified point.
// Currently, rotation and scaling transforms are not supported.
func (dc *Context) DrawString(s string, x, y float64) {
dc.DrawStringAnchored(s, x, y, 0, 0)
}
// DrawStringAnchored draws the specified text at the specified anchor point.
// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
// text. Use ax=0.5, ay=0.5 to center the text at the specified point.
func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) {
w, h := dc.MeasureString(s)
x, y = dc.TransformPoint(x, y)
x -= ax * w
y += ay * h
if dc.mask == nil {
dc.drawString(dc.im, s, x, y)
} else {
im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
dc.drawString(im, s, x, y)
draw.DrawMask(dc.im, dc.im.Bounds(), im, image.ZP, dc.mask, image.ZP, draw.Over)
}
}
// DrawStringWrapped word-wraps the specified string to the given max width
// and then draws it at the specified anchor point using the given line
// spacing and text alignment.
func (dc *Context) DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align) {
lines := dc.WordWrap(s, width)
h := float64(len(lines)) * dc.fontHeight * lineSpacing
h -= (lineSpacing - 1) * dc.fontHeight
x -= ax * width
y -= ay * h
switch align {
case AlignLeft:
ax = 0
case AlignCenter:
ax = 0.5
x += width / 2
case AlignRight:
ax = 1
x += width
}
ay = 1
for _, line := range lines {
dc.DrawStringAnchored(line, x, y, ax, ay)
y += dc.fontHeight * lineSpacing
}
}
// MeasureString returns the rendered width and height of the specified text
// given the current font face.
func (dc *Context) MeasureString(s string) (w, h float64) {
d := &font.Drawer{
Face: dc.fontFace,
}
a := d.MeasureString(s)
return float64(a >> 6), dc.fontHeight
}
// WordWrap wraps the specified string to the given max width and current
// font face.
func (dc *Context) WordWrap(s string, w float64) []string {
return wordWrap(dc, s, w)
}
// Transformation Matrix Operations
// Identity resets the current transformation matrix to the identity matrix.
// This results in no translating, scaling, rotating, or shearing.
func (dc *Context) Identity() {
dc.matrix = Identity()
}
// Translate updates the current matrix with a translation.
func (dc *Context) Translate(x, y float64) {
dc.matrix = dc.matrix.Translate(x, y)
}
// Scale updates the current matrix with a scaling factor.
// Scaling occurs about the origin.
func (dc *Context) Scale(x, y float64) {
dc.matrix = dc.matrix.Scale(x, y)
}
// ScaleAbout updates the current matrix with a scaling factor.
// Scaling occurs about the specified point.
func (dc *Context) ScaleAbout(sx, sy, x, y float64) {
dc.Translate(x, y)
dc.Scale(sx, sy)
dc.Translate(-x, -y)
}
// Rotate updates the current matrix with a clockwise rotation.
// Rotation occurs about the origin. Angle is specified in radians.
func (dc *Context) Rotate(angle float64) {
dc.matrix = dc.matrix.Rotate(angle)
}
// RotateAbout updates the current matrix with a clockwise rotation.
// Rotation occurs about the specified point. Angle is specified in radians.
func (dc *Context) RotateAbout(angle, x, y float64) {
dc.Translate(x, y)
dc.Rotate(angle)
dc.Translate(-x, -y)
}
// Shear updates the current matrix with a shearing angle.
// Shearing occurs about the origin.
func (dc *Context) Shear(x, y float64) {
dc.matrix = dc.matrix.Shear(x, y)
}
// ShearAbout updates the current matrix with a shearing angle.
// Shearing occurs about the specified point.
func (dc *Context) ShearAbout(sx, sy, x, y float64) {
dc.Translate(x, y)
dc.Shear(sx, sy)
dc.Translate(-x, -y)
}
// TransformPoint multiplies the specified point by the current matrix,
// returning a transformed position.
func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) {
return dc.matrix.TransformPoint(x, y)
}
// InvertY flips the Y axis so that Y grows from bottom to top and Y=0 is at
// the bottom of the image.
func (dc *Context) InvertY() {
dc.Translate(0, float64(dc.height))
dc.Scale(1, -1)
}
// Stack
// Push saves the current state of the context for later retrieval. These
// can be nested.
func (dc *Context) Push() {
x := *dc
dc.stack = append(dc.stack, &x)
}
// Pop restores the last saved context state from the stack.
func (dc *Context) Pop() {
before := *dc
s := dc.stack
x, s := s[len(s)-1], s[:len(s)-1]
*dc = *x
dc.mask = before.mask
dc.strokePath = before.strokePath
dc.fillPath = before.fillPath
dc.start = before.start
dc.current = before.current
dc.hasCurrent = before.hasCurrent
}

View file

@ -1,202 +0,0 @@
package gg
import (
"image/color"
"math"
"sort"
)
type stop struct {
pos float64
color color.Color
}
type stops []stop
// Len satisfies the Sort interface.
func (s stops) Len() int {
return len(s)
}
// Less satisfies the Sort interface.
func (s stops) Less(i, j int) bool {
return s[i].pos < s[j].pos
}
// Swap satisfies the Sort interface.
func (s stops) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
type Gradient interface {
Pattern
AddColorStop(offset float64, color color.Color)
}
// Linear Gradient
type linearGradient struct {
x0, y0, x1, y1 float64
stops stops
}
func (g *linearGradient) ColorAt(x, y int) color.Color {
if len(g.stops) == 0 {
return color.Transparent
}
fx, fy := float64(x), float64(y)
x0, y0, x1, y1 := g.x0, g.y0, g.x1, g.y1
dx, dy := x1-x0, y1-y0
// Horizontal
if dy == 0 && dx != 0 {
return getColor((fx-x0)/dx, g.stops)
}
// Vertical
if dx == 0 && dy != 0 {
return getColor((fy-y0)/dy, g.stops)
}
// Dot product
s0 := dx*(fx-x0) + dy*(fy-y0)
if s0 < 0 {
return g.stops[0].color
}
// Calculate distance to (x0,y0) alone (x0,y0)->(x1,y1)
mag := math.Hypot(dx, dy)
u := ((fx-x0)*-dy + (fy-y0)*dx) / (mag * mag)
x2, y2 := x0+u*-dy, y0+u*dx
d := math.Hypot(fx-x2, fy-y2) / mag
return getColor(d, g.stops)
}
func (g *linearGradient) AddColorStop(offset float64, color color.Color) {
g.stops = append(g.stops, stop{pos: offset, color: color})
sort.Sort(g.stops)
}
func NewLinearGradient(x0, y0, x1, y1 float64) Gradient {
g := &linearGradient{
x0: x0, y0: y0,
x1: x1, y1: y1,
}
return g
}
// Radial Gradient
type circle struct {
x, y, r float64
}
type radialGradient struct {
c0, c1, cd circle
a, inva float64
mindr float64
stops stops
}
func dot3(x0, y0, z0, x1, y1, z1 float64) float64 {
return x0*x1 + y0*y1 + z0*z1
}
func (g *radialGradient) ColorAt(x, y int) color.Color {
if len(g.stops) == 0 {
return color.Transparent
}
// copy from pixman's pixman-radial-gradient.c
dx, dy := float64(x)+0.5-g.c0.x, float64(y)+0.5-g.c0.y
b := dot3(dx, dy, g.c0.r, g.cd.x, g.cd.y, g.cd.r)
c := dot3(dx, dy, -g.c0.r, dx, dy, g.c0.r)
if g.a == 0 {
if b == 0 {
return color.Transparent
}
t := 0.5 * c / b
if t*g.cd.r >= g.mindr {
return getColor(t, g.stops)
}
return color.Transparent
}
discr := dot3(b, g.a, 0, b, -c, 0)
if discr >= 0 {
sqrtdiscr := math.Sqrt(discr)
t0 := (b + sqrtdiscr) * g.inva
t1 := (b - sqrtdiscr) * g.inva
if t0*g.cd.r >= g.mindr {
return getColor(t0, g.stops)
} else if t1*g.cd.r >= g.mindr {
return getColor(t1, g.stops)
}
}
return color.Transparent
}
func (g *radialGradient) AddColorStop(offset float64, color color.Color) {
g.stops = append(g.stops, stop{pos: offset, color: color})
sort.Sort(g.stops)
}
func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) Gradient {
c0 := circle{x0, y0, r0}
c1 := circle{x1, y1, r1}
cd := circle{x1 - x0, y1 - y0, r1 - r0}
a := dot3(cd.x, cd.y, -cd.r, cd.x, cd.y, cd.r)
var inva float64
if a != 0 {
inva = 1.0 / a
}
mindr := -c0.r
g := &radialGradient{
c0: c0,
c1: c1,
cd: cd,
a: a,
inva: inva,
mindr: mindr,
}
return g
}
func getColor(pos float64, stops stops) color.Color {
if pos <= 0.0 || len(stops) == 1 {
return stops[0].color
}
last := stops[len(stops)-1]
if pos >= last.pos {
return last.color
}
for i, stop := range stops[1:] {
if pos < stop.pos {
pos = (pos - stops[i].pos) / (stop.pos - stops[i].pos)
return colorLerp(stops[i].color, stop.color, pos)
}
}
return last.color
}
func colorLerp(c0, c1 color.Color, t float64) color.Color {
r0, g0, b0, a0 := c0.RGBA()
r1, g1, b1, a1 := c1.RGBA()
return color.NRGBA{
lerp(r0, r1, t),
lerp(g0, g1, t),
lerp(b0, b1, t),
lerp(a0, a1, t),
}
}
func lerp(a, b uint32, t float64) uint8 {
return uint8(int32(float64(a)*(1.0-t)+float64(b)*t) >> 8)
}

View file

@ -1,88 +0,0 @@
package gg
import "math"
type Matrix struct {
XX, YX, XY, YY, X0, Y0 float64
}
func Identity() Matrix {
return Matrix{
1, 0,
0, 1,
0, 0,
}
}
func Translate(x, y float64) Matrix {
return Matrix{
1, 0,
0, 1,
x, y,
}
}
func Scale(x, y float64) Matrix {
return Matrix{
x, 0,
0, y,
0, 0,
}
}
func Rotate(angle float64) Matrix {
c := math.Cos(angle)
s := math.Sin(angle)
return Matrix{
c, s,
-s, c,
0, 0,
}
}
func Shear(x, y float64) Matrix {
return Matrix{
1, y,
x, 1,
0, 0,
}
}
func (a Matrix) Multiply(b Matrix) Matrix {
return Matrix{
a.XX*b.XX + a.YX*b.XY,
a.XX*b.YX + a.YX*b.YY,
a.XY*b.XX + a.YY*b.XY,
a.XY*b.YX + a.YY*b.YY,
a.X0*b.XX + a.Y0*b.XY + b.X0,
a.X0*b.YX + a.Y0*b.YY + b.Y0,
}
}
func (a Matrix) TransformVector(x, y float64) (tx, ty float64) {
tx = a.XX*x + a.XY*y
ty = a.YX*x + a.YY*y
return
}
func (a Matrix) TransformPoint(x, y float64) (tx, ty float64) {
tx = a.XX*x + a.XY*y + a.X0
ty = a.YX*x + a.YY*y + a.Y0
return
}
func (a Matrix) Translate(x, y float64) Matrix {
return Translate(x, y).Multiply(a)
}
func (a Matrix) Scale(x, y float64) Matrix {
return Scale(x, y).Multiply(a)
}
func (a Matrix) Rotate(angle float64) Matrix {
return Rotate(angle).Multiply(a)
}
func (a Matrix) Shear(x, y float64) Matrix {
return Shear(x, y).Multiply(a)
}

140
vendor/github.com/fogleman/gg/path.go generated vendored
View file

@ -1,140 +0,0 @@
package gg
import (
"github.com/golang/freetype/raster"
"golang.org/x/image/math/fixed"
)
func flattenPath(p raster.Path) [][]Point {
var result [][]Point
var path []Point
var cx, cy float64
for i := 0; i < len(p); {
switch p[i] {
case 0:
if len(path) > 0 {
result = append(result, path)
path = nil
}
x := unfix(p[i+1])
y := unfix(p[i+2])
path = append(path, Point{x, y})
cx, cy = x, y
i += 4
case 1:
x := unfix(p[i+1])
y := unfix(p[i+2])
path = append(path, Point{x, y})
cx, cy = x, y
i += 4
case 2:
x1 := unfix(p[i+1])
y1 := unfix(p[i+2])
x2 := unfix(p[i+3])
y2 := unfix(p[i+4])
points := QuadraticBezier(cx, cy, x1, y1, x2, y2)
path = append(path, points...)
cx, cy = x2, y2
i += 6
case 3:
x1 := unfix(p[i+1])
y1 := unfix(p[i+2])
x2 := unfix(p[i+3])
y2 := unfix(p[i+4])
x3 := unfix(p[i+5])
y3 := unfix(p[i+6])
points := CubicBezier(cx, cy, x1, y1, x2, y2, x3, y3)
path = append(path, points...)
cx, cy = x3, y3
i += 8
default:
panic("bad path")
}
}
if len(path) > 0 {
result = append(result, path)
}
return result
}
func dashPath(paths [][]Point, dashes []float64) [][]Point {
var result [][]Point
if len(dashes) == 0 {
return paths
}
if len(dashes) == 1 {
dashes = append(dashes, dashes[0])
}
for _, path := range paths {
if len(path) < 2 {
continue
}
previous := path[0]
pathIndex := 1
dashIndex := 0
segmentLength := 0.0
var segment []Point
segment = append(segment, previous)
for pathIndex < len(path) {
dashLength := dashes[dashIndex]
point := path[pathIndex]
d := previous.Distance(point)
maxd := dashLength - segmentLength
if d > maxd {
t := maxd / d
p := previous.Interpolate(point, t)
segment = append(segment, p)
if dashIndex%2 == 0 && len(segment) > 1 {
result = append(result, segment)
}
segment = nil
segment = append(segment, p)
segmentLength = 0
previous = p
dashIndex = (dashIndex + 1) % len(dashes)
} else {
segment = append(segment, point)
previous = point
segmentLength += d
pathIndex++
}
}
if dashIndex%2 == 0 && len(segment) > 1 {
result = append(result, segment)
}
}
return result
}
func rasterPath(paths [][]Point) raster.Path {
var result raster.Path
for _, path := range paths {
var previous fixed.Point26_6
for i, point := range path {
f := point.Fixed()
if i == 0 {
result.Start(f)
} else {
dx := f.X - previous.X
dy := f.Y - previous.Y
if dx < 0 {
dx = -dx
}
if dy < 0 {
dy = -dy
}
if dx+dy > 8 {
// TODO: this is a hack for cases where two points are
// too close - causes rendering issues with joins / caps
result.Add1(f)
}
}
previous = f
}
}
return result
}
func dashed(path raster.Path, dashes []float64) raster.Path {
return rasterPath(dashPath(flattenPath(path), dashes))
}

View file

@ -1,123 +0,0 @@
package gg
import (
"image"
"image/color"
"github.com/golang/freetype/raster"
)
type RepeatOp int
const (
RepeatBoth RepeatOp = iota
RepeatX
RepeatY
RepeatNone
)
type Pattern interface {
ColorAt(x, y int) color.Color
}
// Solid Pattern
type solidPattern struct {
color color.Color
}
func (p *solidPattern) ColorAt(x, y int) color.Color {
return p.color
}
func NewSolidPattern(color color.Color) Pattern {
return &solidPattern{color: color}
}
// Surface Pattern
type surfacePattern struct {
im image.Image
op RepeatOp
}
func (p *surfacePattern) ColorAt(x, y int) color.Color {
b := p.im.Bounds()
switch p.op {
case RepeatX:
if y >= b.Dy() {
return color.Transparent
}
case RepeatY:
if x >= b.Dx() {
return color.Transparent
}
case RepeatNone:
if x >= b.Dx() || y >= b.Dy() {
return color.Transparent
}
}
x = x%b.Dx() + b.Min.X
y = y%b.Dy() + b.Min.Y
return p.im.At(x, y)
}
func NewSurfacePattern(im image.Image, op RepeatOp) Pattern {
return &surfacePattern{im: im, op: op}
}
type patternPainter struct {
im *image.RGBA
mask *image.Alpha
p Pattern
}
// Paint satisfies the Painter interface.
func (r *patternPainter) Paint(ss []raster.Span, done bool) {
b := r.im.Bounds()
for _, s := range ss {
if s.Y < b.Min.Y {
continue
}
if s.Y >= b.Max.Y {
return
}
if s.X0 < b.Min.X {
s.X0 = b.Min.X
}
if s.X1 > b.Max.X {
s.X1 = b.Max.X
}
if s.X0 >= s.X1 {
continue
}
const m = 1<<16 - 1
y := s.Y - r.im.Rect.Min.Y
x0 := s.X0 - r.im.Rect.Min.X
// RGBAPainter.Paint() in $GOPATH/src/github.com/golang/freetype/raster/paint.go
i0 := (s.Y-r.im.Rect.Min.Y)*r.im.Stride + (s.X0-r.im.Rect.Min.X)*4
i1 := i0 + (s.X1-s.X0)*4
for i, x := i0, x0; i < i1; i, x = i+4, x+1 {
ma := s.Alpha
if r.mask != nil {
ma = ma * uint32(r.mask.AlphaAt(x, y).A) / 255
if ma == 0 {
continue
}
}
c := r.p.ColorAt(x, y)
cr, cg, cb, ca := c.RGBA()
dr := uint32(r.im.Pix[i+0])
dg := uint32(r.im.Pix[i+1])
db := uint32(r.im.Pix[i+2])
da := uint32(r.im.Pix[i+3])
a := (m - (ca * ma / m)) * 0x101
r.im.Pix[i+0] = uint8((dr*a + cr*ma) / m >> 8)
r.im.Pix[i+1] = uint8((dg*a + cg*ma) / m >> 8)
r.im.Pix[i+2] = uint8((db*a + cb*ma) / m >> 8)
r.im.Pix[i+3] = uint8((da*a + ca*ma) / m >> 8)
}
}
}
func newPatternPainter(im *image.RGBA, mask *image.Alpha, p Pattern) *patternPainter {
return &patternPainter{im, mask, p}
}

View file

@ -1,25 +0,0 @@
package gg
import (
"math"
"golang.org/x/image/math/fixed"
)
type Point struct {
X, Y float64
}
func (a Point) Fixed() fixed.Point26_6 {
return fixp(a.X, a.Y)
}
func (a Point) Distance(b Point) float64 {
return math.Hypot(a.X-b.X, a.Y-b.Y)
}
func (a Point) Interpolate(b Point, t float64) Point {
x := a.X + (b.X-a.X)*t
y := a.Y + (b.Y-a.Y)*t
return Point{x, y}
}

117
vendor/github.com/fogleman/gg/util.go generated vendored
View file

@ -1,117 +0,0 @@
package gg
import (
"fmt"
"image"
"image/draw"
_ "image/jpeg"
"image/png"
"io/ioutil"
"math"
"os"
"strings"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
func Radians(degrees float64) float64 {
return degrees * math.Pi / 180
}
func Degrees(radians float64) float64 {
return radians * 180 / math.Pi
}
func LoadImage(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
im, _, err := image.Decode(file)
return im, err
}
func LoadPNG(path string) (image.Image, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return png.Decode(file)
}
func SavePNG(path string, im image.Image) error {
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return png.Encode(file, im)
}
func imageToRGBA(src image.Image) *image.RGBA {
dst := image.NewRGBA(src.Bounds())
draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
return dst
}
func parseHexColor(x string) (r, g, b, a int) {
x = strings.TrimPrefix(x, "#")
a = 255
if len(x) == 3 {
format := "%1x%1x%1x"
fmt.Sscanf(x, format, &r, &g, &b)
r |= r << 4
g |= g << 4
b |= b << 4
}
if len(x) == 6 {
format := "%02x%02x%02x"
fmt.Sscanf(x, format, &r, &g, &b)
}
if len(x) == 8 {
format := "%02x%02x%02x%02x"
fmt.Sscanf(x, format, &r, &g, &b, &a)
}
return
}
func fixp(x, y float64) fixed.Point26_6 {
return fixed.Point26_6{fix(x), fix(y)}
}
func fix(x float64) fixed.Int26_6 {
return fixed.Int26_6(x * 64)
}
func unfix(x fixed.Int26_6) float64 {
const shift, mask = 6, 1<<6 - 1
if x >= 0 {
return float64(x>>shift) + float64(x&mask)/64
}
x = -x
if x >= 0 {
return -(float64(x>>shift) + float64(x&mask)/64)
}
return 0
}
func LoadFontFace(path string, points float64) (font.Face, error) {
fontBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
f, err := truetype.Parse(fontBytes)
if err != nil {
return nil, err
}
face := truetype.NewFace(f, &truetype.Options{
Size: points,
// Hinting: font.HintingFull,
})
return face, nil
}

View file

@ -1,58 +0,0 @@
package gg
import (
"strings"
"unicode"
)
type measureStringer interface {
MeasureString(s string) (w, h float64)
}
func splitOnSpace(x string) []string {
var result []string
pi := 0
ps := false
for i, c := range x {
s := unicode.IsSpace(c)
if s != ps && i > 0 {
result = append(result, x[pi:i])
pi = i
}
ps = s
}
result = append(result, x[pi:])
return result
}
func wordWrap(m measureStringer, s string, width float64) []string {
var result []string
for _, line := range strings.Split(s, "\n") {
fields := splitOnSpace(line)
if len(fields)%2 == 1 {
fields = append(fields, "")
}
x := ""
for i := 0; i < len(fields); i += 2 {
w, _ := m.MeasureString(x + fields[i])
if w > width {
if x == "" {
result = append(result, fields[i])
x = ""
continue
} else {
result = append(result, x)
x = ""
}
}
x += fields[i] + fields[i+1]
}
if x != "" {
result = append(result, x)
}
}
for i, line := range result {
result[i] = strings.TrimSpace(line)
}
return result
}

View file

@ -1,20 +0,0 @@
# This is the official list of Freetype-Go authors for copyright purposes.
# This file is distinct from the CONTRIBUTORS files.
# See the latter for an explanation.
#
# Freetype-Go is derived from Freetype, which is written in C. The latter
# is copyright 1996-2010 David Turner, Robert Wilhelm, and Werner Lemberg.
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# Please keep the list sorted.
Google Inc.
Jeff R. Allen <jra@nella.org>
Maksim Kochkin <maxxarts@gmail.com>
Michael Fogleman <fogleman@gmail.com>
Rémy Oudompheng <oudomphe@phare.normalesup.org>
Roger Peppe <rogpeppe@gmail.com>
Steven Edwards <steven@stephenwithav.com>

View file

@ -1,38 +0,0 @@
# This is the official list of people who can contribute
# (and typically have contributed) code to the Freetype-Go repository.
# The AUTHORS file lists the copyright holders; this file
# lists people. For example, Google employees are listed here
# but not in AUTHORS, because Google holds the copyright.
#
# The submission process automatically checks to make sure
# that people submitting code are listed in this file (by email address).
#
# Names should be added to this file only after verifying that
# the individual or the individual's organization has agreed to
# the appropriate Contributor License Agreement, found here:
#
# http://code.google.com/legal/individual-cla-v1.0.html
# http://code.google.com/legal/corporate-cla-v1.0.html
#
# The agreement for individuals can be filled out on the web.
#
# When adding J Random Contributor's name to this file,
# either J's name or J's organization's name should be
# added to the AUTHORS file, depending on whether the
# individual or corporate CLA was used.
# Names should be added to this file like so:
# Name <email address>
# Please keep the list sorted.
Andrew Gerrand <adg@golang.org>
Jeff R. Allen <jra@nella.org> <jeff.allen@gmail.com>
Maksim Kochkin <maxxarts@gmail.com>
Michael Fogleman <fogleman@gmail.com>
Nigel Tao <nigeltao@golang.org>
Rémy Oudompheng <oudomphe@phare.normalesup.org> <remyoudompheng@gmail.com>
Rob Pike <r@golang.org>
Roger Peppe <rogpeppe@gmail.com>
Russ Cox <rsc@golang.org>
Steven Edwards <steven@stephenwithav.com>

View file

@ -1,12 +0,0 @@
Use of the Freetype-Go software is subject to your choice of exactly one of
the following two licenses:
* The FreeType License, which is similar to the original BSD license with
an advertising clause, or
* The GNU General Public License (GPL), version 2 or later.
The text of these licenses are available in the licenses/ftl.txt and the
licenses/gpl.txt files respectively. They are also available at
http://freetype.sourceforge.net/license.html
The Luxi fonts in the testdata directory are licensed separately. See the
testdata/COPYING file for details.

View file

@ -1,245 +0,0 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package raster
import (
"fmt"
"math"
"golang.org/x/image/math/fixed"
)
// maxAbs returns the maximum of abs(a) and abs(b).
func maxAbs(a, b fixed.Int26_6) fixed.Int26_6 {
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
if a < b {
return b
}
return a
}
// pNeg returns the vector -p, or equivalently p rotated by 180 degrees.
func pNeg(p fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{-p.X, -p.Y}
}
// pDot returns the dot product p·q.
func pDot(p fixed.Point26_6, q fixed.Point26_6) fixed.Int52_12 {
px, py := int64(p.X), int64(p.Y)
qx, qy := int64(q.X), int64(q.Y)
return fixed.Int52_12(px*qx + py*qy)
}
// pLen returns the length of the vector p.
func pLen(p fixed.Point26_6) fixed.Int26_6 {
// TODO(nigeltao): use fixed point math.
x := float64(p.X)
y := float64(p.Y)
return fixed.Int26_6(math.Sqrt(x*x + y*y))
}
// pNorm returns the vector p normalized to the given length, or zero if p is
// degenerate.
func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 {
d := pLen(p)
if d == 0 {
return fixed.Point26_6{}
}
s, t := int64(length), int64(d)
x := int64(p.X) * s / t
y := int64(p.Y) * s / t
return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)}
}
// pRot45CW returns the vector p rotated clockwise by 45 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CW is {1/√2, 1/√2}.
func pRot45CW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (+px - py) * 181 / 256
qy := (+px + py) * 181 / 256
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// pRot90CW returns the vector p rotated clockwise by 90 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CW is {0, 1}.
func pRot90CW(p fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{-p.Y, p.X}
}
// pRot135CW returns the vector p rotated clockwise by 135 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CW is {-1/√2, 1/√2}.
func pRot135CW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (-px - py) * 181 / 256
qy := (+px - py) * 181 / 256
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}.
func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (+px + py) * 181 / 256
qy := (-px + py) * 181 / 256
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// pRot90CCW returns the vector p rotated counter-clockwise by 90 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot90CCW is {0, -1}.
func pRot90CCW(p fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{p.Y, -p.X}
}
// pRot135CCW returns the vector p rotated counter-clockwise by 135 degrees.
//
// Note that the Y-axis grows downwards, so {1, 0}.Rot135CCW is {-1/√2, -1/√2}.
func pRot135CCW(p fixed.Point26_6) fixed.Point26_6 {
// 181/256 is approximately 1/√2, or sin(π/4).
px, py := int64(p.X), int64(p.Y)
qx := (-px + py) * 181 / 256
qy := (-px - py) * 181 / 256
return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)}
}
// An Adder accumulates points on a curve.
type Adder interface {
// Start starts a new curve at the given point.
Start(a fixed.Point26_6)
// Add1 adds a linear segment to the current curve.
Add1(b fixed.Point26_6)
// Add2 adds a quadratic segment to the current curve.
Add2(b, c fixed.Point26_6)
// Add3 adds a cubic segment to the current curve.
Add3(b, c, d fixed.Point26_6)
}
// A Path is a sequence of curves, and a curve is a start point followed by a
// sequence of linear, quadratic or cubic segments.
type Path []fixed.Int26_6
// String returns a human-readable representation of a Path.
func (p Path) String() string {
s := ""
for i := 0; i < len(p); {
if i != 0 {
s += " "
}
switch p[i] {
case 0:
s += "S0" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
i += 4
case 1:
s += "A1" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+3]))
i += 4
case 2:
s += "A2" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+5]))
i += 6
case 3:
s += "A3" + fmt.Sprint([]fixed.Int26_6(p[i+1:i+7]))
i += 8
default:
panic("freetype/raster: bad path")
}
}
return s
}
// Clear cancels any previous calls to p.Start or p.AddXxx.
func (p *Path) Clear() {
*p = (*p)[:0]
}
// Start starts a new curve at the given point.
func (p *Path) Start(a fixed.Point26_6) {
*p = append(*p, 0, a.X, a.Y, 0)
}
// Add1 adds a linear segment to the current curve.
func (p *Path) Add1(b fixed.Point26_6) {
*p = append(*p, 1, b.X, b.Y, 1)
}
// Add2 adds a quadratic segment to the current curve.
func (p *Path) Add2(b, c fixed.Point26_6) {
*p = append(*p, 2, b.X, b.Y, c.X, c.Y, 2)
}
// Add3 adds a cubic segment to the current curve.
func (p *Path) Add3(b, c, d fixed.Point26_6) {
*p = append(*p, 3, b.X, b.Y, c.X, c.Y, d.X, d.Y, 3)
}
// AddPath adds the Path q to p.
func (p *Path) AddPath(q Path) {
*p = append(*p, q...)
}
// AddStroke adds a stroked Path.
func (p *Path) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
Stroke(p, q, width, cr, jr)
}
// firstPoint returns the first point in a non-empty Path.
func (p Path) firstPoint() fixed.Point26_6 {
return fixed.Point26_6{p[1], p[2]}
}
// lastPoint returns the last point in a non-empty Path.
func (p Path) lastPoint() fixed.Point26_6 {
return fixed.Point26_6{p[len(p)-3], p[len(p)-2]}
}
// addPathReversed adds q reversed to p.
// For example, if q consists of a linear segment from A to B followed by a
// quadratic segment from B to C to D, then the values of q looks like:
// index: 01234567890123
// value: 0AA01BB12CCDD2
// So, when adding q backwards to p, we want to Add2(C, B) followed by Add1(A).
func addPathReversed(p Adder, q Path) {
if len(q) == 0 {
return
}
i := len(q) - 1
for {
switch q[i] {
case 0:
return
case 1:
i -= 4
p.Add1(
fixed.Point26_6{q[i-2], q[i-1]},
)
case 2:
i -= 6
p.Add2(
fixed.Point26_6{q[i+2], q[i+3]},
fixed.Point26_6{q[i-2], q[i-1]},
)
case 3:
i -= 8
p.Add3(
fixed.Point26_6{q[i+4], q[i+5]},
fixed.Point26_6{q[i+2], q[i+3]},
fixed.Point26_6{q[i-2], q[i-1]},
)
default:
panic("freetype/raster: bad path")
}
}
}

View file

@ -1,287 +0,0 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package raster
import (
"image"
"image/color"
"image/draw"
"math"
)
// A Span is a horizontal segment of pixels with constant alpha. X0 is an
// inclusive bound and X1 is exclusive, the same as for slices. A fully opaque
// Span has Alpha == 0xffff.
type Span struct {
Y, X0, X1 int
Alpha uint32
}
// A Painter knows how to paint a batch of Spans. Rasterization may involve
// Painting multiple batches, and done will be true for the final batch. The
// Spans' Y values are monotonically increasing during a rasterization. Paint
// may use all of ss as scratch space during the call.
type Painter interface {
Paint(ss []Span, done bool)
}
// The PainterFunc type adapts an ordinary function to the Painter interface.
type PainterFunc func(ss []Span, done bool)
// Paint just delegates the call to f.
func (f PainterFunc) Paint(ss []Span, done bool) { f(ss, done) }
// An AlphaOverPainter is a Painter that paints Spans onto a *image.Alpha using
// the Over Porter-Duff composition operator.
type AlphaOverPainter struct {
Image *image.Alpha
}
// Paint satisfies the Painter interface.
func (r AlphaOverPainter) Paint(ss []Span, done bool) {
b := r.Image.Bounds()
for _, s := range ss {
if s.Y < b.Min.Y {
continue
}
if s.Y >= b.Max.Y {
return
}
if s.X0 < b.Min.X {
s.X0 = b.Min.X
}
if s.X1 > b.Max.X {
s.X1 = b.Max.X
}
if s.X0 >= s.X1 {
continue
}
base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X
p := r.Image.Pix[base+s.X0 : base+s.X1]
a := int(s.Alpha >> 8)
for i, c := range p {
v := int(c)
p[i] = uint8((v*255 + (255-v)*a) / 255)
}
}
}
// NewAlphaOverPainter creates a new AlphaOverPainter for the given image.
func NewAlphaOverPainter(m *image.Alpha) AlphaOverPainter {
return AlphaOverPainter{m}
}
// An AlphaSrcPainter is a Painter that paints Spans onto a *image.Alpha using
// the Src Porter-Duff composition operator.
type AlphaSrcPainter struct {
Image *image.Alpha
}
// Paint satisfies the Painter interface.
func (r AlphaSrcPainter) Paint(ss []Span, done bool) {
b := r.Image.Bounds()
for _, s := range ss {
if s.Y < b.Min.Y {
continue
}
if s.Y >= b.Max.Y {
return
}
if s.X0 < b.Min.X {
s.X0 = b.Min.X
}
if s.X1 > b.Max.X {
s.X1 = b.Max.X
}
if s.X0 >= s.X1 {
continue
}
base := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride - r.Image.Rect.Min.X
p := r.Image.Pix[base+s.X0 : base+s.X1]
color := uint8(s.Alpha >> 8)
for i := range p {
p[i] = color
}
}
}
// NewAlphaSrcPainter creates a new AlphaSrcPainter for the given image.
func NewAlphaSrcPainter(m *image.Alpha) AlphaSrcPainter {
return AlphaSrcPainter{m}
}
// An RGBAPainter is a Painter that paints Spans onto a *image.RGBA.
type RGBAPainter struct {
// Image is the image to compose onto.
Image *image.RGBA
// Op is the Porter-Duff composition operator.
Op draw.Op
// cr, cg, cb and ca are the 16-bit color to paint the spans.
cr, cg, cb, ca uint32
}
// Paint satisfies the Painter interface.
func (r *RGBAPainter) Paint(ss []Span, done bool) {
b := r.Image.Bounds()
for _, s := range ss {
if s.Y < b.Min.Y {
continue
}
if s.Y >= b.Max.Y {
return
}
if s.X0 < b.Min.X {
s.X0 = b.Min.X
}
if s.X1 > b.Max.X {
s.X1 = b.Max.X
}
if s.X0 >= s.X1 {
continue
}
// This code mimics drawGlyphOver in $GOROOT/src/image/draw/draw.go.
ma := s.Alpha
const m = 1<<16 - 1
i0 := (s.Y-r.Image.Rect.Min.Y)*r.Image.Stride + (s.X0-r.Image.Rect.Min.X)*4
i1 := i0 + (s.X1-s.X0)*4
if r.Op == draw.Over {
for i := i0; i < i1; i += 4 {
dr := uint32(r.Image.Pix[i+0])
dg := uint32(r.Image.Pix[i+1])
db := uint32(r.Image.Pix[i+2])
da := uint32(r.Image.Pix[i+3])
a := (m - (r.ca * ma / m)) * 0x101
r.Image.Pix[i+0] = uint8((dr*a + r.cr*ma) / m >> 8)
r.Image.Pix[i+1] = uint8((dg*a + r.cg*ma) / m >> 8)
r.Image.Pix[i+2] = uint8((db*a + r.cb*ma) / m >> 8)
r.Image.Pix[i+3] = uint8((da*a + r.ca*ma) / m >> 8)
}
} else {
for i := i0; i < i1; i += 4 {
r.Image.Pix[i+0] = uint8(r.cr * ma / m >> 8)
r.Image.Pix[i+1] = uint8(r.cg * ma / m >> 8)
r.Image.Pix[i+2] = uint8(r.cb * ma / m >> 8)
r.Image.Pix[i+3] = uint8(r.ca * ma / m >> 8)
}
}
}
}
// SetColor sets the color to paint the spans.
func (r *RGBAPainter) SetColor(c color.Color) {
r.cr, r.cg, r.cb, r.ca = c.RGBA()
}
// NewRGBAPainter creates a new RGBAPainter for the given image.
func NewRGBAPainter(m *image.RGBA) *RGBAPainter {
return &RGBAPainter{Image: m}
}
// A MonochromePainter wraps another Painter, quantizing each Span's alpha to
// be either fully opaque or fully transparent.
type MonochromePainter struct {
Painter Painter
y, x0, x1 int
}
// Paint delegates to the wrapped Painter after quantizing each Span's alpha
// value and merging adjacent fully opaque Spans.
func (m *MonochromePainter) Paint(ss []Span, done bool) {
// We compact the ss slice, discarding any Spans whose alpha quantizes to zero.
j := 0
for _, s := range ss {
if s.Alpha >= 0x8000 {
if m.y == s.Y && m.x1 == s.X0 {
m.x1 = s.X1
} else {
ss[j] = Span{m.y, m.x0, m.x1, 1<<16 - 1}
j++
m.y, m.x0, m.x1 = s.Y, s.X0, s.X1
}
}
}
if done {
// Flush the accumulated Span.
finalSpan := Span{m.y, m.x0, m.x1, 1<<16 - 1}
if j < len(ss) {
ss[j] = finalSpan
j++
m.Painter.Paint(ss[:j], true)
} else if j == len(ss) {
m.Painter.Paint(ss, false)
if cap(ss) > 0 {
ss = ss[:1]
} else {
ss = make([]Span, 1)
}
ss[0] = finalSpan
m.Painter.Paint(ss, true)
} else {
panic("unreachable")
}
// Reset the accumulator, so that this Painter can be re-used.
m.y, m.x0, m.x1 = 0, 0, 0
} else {
m.Painter.Paint(ss[:j], false)
}
}
// NewMonochromePainter creates a new MonochromePainter that wraps the given
// Painter.
func NewMonochromePainter(p Painter) *MonochromePainter {
return &MonochromePainter{Painter: p}
}
// A GammaCorrectionPainter wraps another Painter, performing gamma-correction
// on each Span's alpha value.
type GammaCorrectionPainter struct {
// Painter is the wrapped Painter.
Painter Painter
// a is the precomputed alpha values for linear interpolation, with fully
// opaque == 0xffff.
a [256]uint16
// gammaIsOne is whether gamma correction is a no-op.
gammaIsOne bool
}
// Paint delegates to the wrapped Painter after performing gamma-correction on
// each Span.
func (g *GammaCorrectionPainter) Paint(ss []Span, done bool) {
if !g.gammaIsOne {
const n = 0x101
for i, s := range ss {
if s.Alpha == 0 || s.Alpha == 0xffff {
continue
}
p, q := s.Alpha/n, s.Alpha%n
// The resultant alpha is a linear interpolation of g.a[p] and g.a[p+1].
a := uint32(g.a[p])*(n-q) + uint32(g.a[p+1])*q
ss[i].Alpha = (a + n/2) / n
}
}
g.Painter.Paint(ss, done)
}
// SetGamma sets the gamma value.
func (g *GammaCorrectionPainter) SetGamma(gamma float64) {
g.gammaIsOne = gamma == 1
if g.gammaIsOne {
return
}
for i := 0; i < 256; i++ {
a := float64(i) / 0xff
a = math.Pow(a, gamma)
g.a[i] = uint16(0xffff * a)
}
}
// NewGammaCorrectionPainter creates a new GammaCorrectionPainter that wraps
// the given Painter.
func NewGammaCorrectionPainter(p Painter, gamma float64) *GammaCorrectionPainter {
g := &GammaCorrectionPainter{Painter: p}
g.SetGamma(gamma)
return g
}

View file

@ -1,601 +0,0 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
// Package raster provides an anti-aliasing 2-D rasterizer.
//
// It is part of the larger Freetype suite of font-related packages, but the
// raster package is not specific to font rasterization, and can be used
// standalone without any other Freetype package.
//
// Rasterization is done by the same area/coverage accumulation algorithm as
// the Freetype "smooth" module, and the Anti-Grain Geometry library. A
// description of the area/coverage algorithm is at
// http://projects.tuxee.net/cl-vectors/section-the-cl-aa-algorithm
package raster // import "github.com/golang/freetype/raster"
import (
"strconv"
"golang.org/x/image/math/fixed"
)
// A cell is part of a linked list (for a given yi co-ordinate) of accumulated
// area/coverage for the pixel at (xi, yi).
type cell struct {
xi int
area, cover int
next int
}
type Rasterizer struct {
// If false, the default behavior is to use the even-odd winding fill
// rule during Rasterize.
UseNonZeroWinding bool
// An offset (in pixels) to the painted spans.
Dx, Dy int
// The width of the Rasterizer. The height is implicit in len(cellIndex).
width int
// splitScaleN is the scaling factor used to determine how many times
// to decompose a quadratic or cubic segment into a linear approximation.
splitScale2, splitScale3 int
// The current pen position.
a fixed.Point26_6
// The current cell and its area/coverage being accumulated.
xi, yi int
area, cover int
// Saved cells.
cell []cell
// Linked list of cells, one per row.
cellIndex []int
// Buffers.
cellBuf [256]cell
cellIndexBuf [64]int
spanBuf [64]Span
}
// findCell returns the index in r.cell for the cell corresponding to
// (r.xi, r.yi). The cell is created if necessary.
func (r *Rasterizer) findCell() int {
if r.yi < 0 || r.yi >= len(r.cellIndex) {
return -1
}
xi := r.xi
if xi < 0 {
xi = -1
} else if xi > r.width {
xi = r.width
}
i, prev := r.cellIndex[r.yi], -1
for i != -1 && r.cell[i].xi <= xi {
if r.cell[i].xi == xi {
return i
}
i, prev = r.cell[i].next, i
}
c := len(r.cell)
if c == cap(r.cell) {
buf := make([]cell, c, 4*c)
copy(buf, r.cell)
r.cell = buf[0 : c+1]
} else {
r.cell = r.cell[0 : c+1]
}
r.cell[c] = cell{xi, 0, 0, i}
if prev == -1 {
r.cellIndex[r.yi] = c
} else {
r.cell[prev].next = c
}
return c
}
// saveCell saves any accumulated r.area/r.cover for (r.xi, r.yi).
func (r *Rasterizer) saveCell() {
if r.area != 0 || r.cover != 0 {
i := r.findCell()
if i != -1 {
r.cell[i].area += r.area
r.cell[i].cover += r.cover
}
r.area = 0
r.cover = 0
}
}
// setCell sets the (xi, yi) cell that r is accumulating area/coverage for.
func (r *Rasterizer) setCell(xi, yi int) {
if r.xi != xi || r.yi != yi {
r.saveCell()
r.xi, r.yi = xi, yi
}
}
// scan accumulates area/coverage for the yi'th scanline, going from
// x0 to x1 in the horizontal direction (in 26.6 fixed point co-ordinates)
// and from y0f to y1f fractional vertical units within that scanline.
func (r *Rasterizer) scan(yi int, x0, y0f, x1, y1f fixed.Int26_6) {
// Break the 26.6 fixed point X co-ordinates into integral and fractional parts.
x0i := int(x0) / 64
x0f := x0 - fixed.Int26_6(64*x0i)
x1i := int(x1) / 64
x1f := x1 - fixed.Int26_6(64*x1i)
// A perfectly horizontal scan.
if y0f == y1f {
r.setCell(x1i, yi)
return
}
dx, dy := x1-x0, y1f-y0f
// A single cell scan.
if x0i == x1i {
r.area += int((x0f + x1f) * dy)
r.cover += int(dy)
return
}
// There are at least two cells. Apart from the first and last cells,
// all intermediate cells go through the full width of the cell,
// or 64 units in 26.6 fixed point format.
var (
p, q, edge0, edge1 fixed.Int26_6
xiDelta int
)
if dx > 0 {
p, q = (64-x0f)*dy, dx
edge0, edge1, xiDelta = 0, 64, 1
} else {
p, q = x0f*dy, -dx
edge0, edge1, xiDelta = 64, 0, -1
}
yDelta, yRem := p/q, p%q
if yRem < 0 {
yDelta -= 1
yRem += q
}
// Do the first cell.
xi, y := x0i, y0f
r.area += int((x0f + edge1) * yDelta)
r.cover += int(yDelta)
xi, y = xi+xiDelta, y+yDelta
r.setCell(xi, yi)
if xi != x1i {
// Do all the intermediate cells.
p = 64 * (y1f - y + yDelta)
fullDelta, fullRem := p/q, p%q
if fullRem < 0 {
fullDelta -= 1
fullRem += q
}
yRem -= q
for xi != x1i {
yDelta = fullDelta
yRem += fullRem
if yRem >= 0 {
yDelta += 1
yRem -= q
}
r.area += int(64 * yDelta)
r.cover += int(yDelta)
xi, y = xi+xiDelta, y+yDelta
r.setCell(xi, yi)
}
}
// Do the last cell.
yDelta = y1f - y
r.area += int((edge0 + x1f) * yDelta)
r.cover += int(yDelta)
}
// Start starts a new curve at the given point.
func (r *Rasterizer) Start(a fixed.Point26_6) {
r.setCell(int(a.X/64), int(a.Y/64))
r.a = a
}
// Add1 adds a linear segment to the current curve.
func (r *Rasterizer) Add1(b fixed.Point26_6) {
x0, y0 := r.a.X, r.a.Y
x1, y1 := b.X, b.Y
dx, dy := x1-x0, y1-y0
// Break the 26.6 fixed point Y co-ordinates into integral and fractional
// parts.
y0i := int(y0) / 64
y0f := y0 - fixed.Int26_6(64*y0i)
y1i := int(y1) / 64
y1f := y1 - fixed.Int26_6(64*y1i)
if y0i == y1i {
// There is only one scanline.
r.scan(y0i, x0, y0f, x1, y1f)
} else if dx == 0 {
// This is a vertical line segment. We avoid calling r.scan and instead
// manipulate r.area and r.cover directly.
var (
edge0, edge1 fixed.Int26_6
yiDelta int
)
if dy > 0 {
edge0, edge1, yiDelta = 0, 64, 1
} else {
edge0, edge1, yiDelta = 64, 0, -1
}
x0i, yi := int(x0)/64, y0i
x0fTimes2 := (int(x0) - (64 * x0i)) * 2
// Do the first pixel.
dcover := int(edge1 - y0f)
darea := int(x0fTimes2 * dcover)
r.area += darea
r.cover += dcover
yi += yiDelta
r.setCell(x0i, yi)
// Do all the intermediate pixels.
dcover = int(edge1 - edge0)
darea = int(x0fTimes2 * dcover)
for yi != y1i {
r.area += darea
r.cover += dcover
yi += yiDelta
r.setCell(x0i, yi)
}
// Do the last pixel.
dcover = int(y1f - edge0)
darea = int(x0fTimes2 * dcover)
r.area += darea
r.cover += dcover
} else {
// There are at least two scanlines. Apart from the first and last
// scanlines, all intermediate scanlines go through the full height of
// the row, or 64 units in 26.6 fixed point format.
var (
p, q, edge0, edge1 fixed.Int26_6
yiDelta int
)
if dy > 0 {
p, q = (64-y0f)*dx, dy
edge0, edge1, yiDelta = 0, 64, 1
} else {
p, q = y0f*dx, -dy
edge0, edge1, yiDelta = 64, 0, -1
}
xDelta, xRem := p/q, p%q
if xRem < 0 {
xDelta -= 1
xRem += q
}
// Do the first scanline.
x, yi := x0, y0i
r.scan(yi, x, y0f, x+xDelta, edge1)
x, yi = x+xDelta, yi+yiDelta
r.setCell(int(x)/64, yi)
if yi != y1i {
// Do all the intermediate scanlines.
p = 64 * dx
fullDelta, fullRem := p/q, p%q
if fullRem < 0 {
fullDelta -= 1
fullRem += q
}
xRem -= q
for yi != y1i {
xDelta = fullDelta
xRem += fullRem
if xRem >= 0 {
xDelta += 1
xRem -= q
}
r.scan(yi, x, edge0, x+xDelta, edge1)
x, yi = x+xDelta, yi+yiDelta
r.setCell(int(x)/64, yi)
}
}
// Do the last scanline.
r.scan(yi, x, edge0, x1, y1f)
}
// The next lineTo starts from b.
r.a = b
}
// Add2 adds a quadratic segment to the current curve.
func (r *Rasterizer) Add2(b, c fixed.Point26_6) {
// Calculate nSplit (the number of recursive decompositions) based on how
// 'curvy' it is. Specifically, how much the middle point b deviates from
// (a+c)/2.
dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2)
nsplit := 0
for dev > 0 {
dev /= 4
nsplit++
}
// dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit
// is 16.
const maxNsplit = 16
if nsplit > maxNsplit {
panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit))
}
// Recursively decompose the curve nSplit levels deep.
var (
pStack [2*maxNsplit + 3]fixed.Point26_6
sStack [maxNsplit + 1]int
i int
)
sStack[0] = nsplit
pStack[0] = c
pStack[1] = b
pStack[2] = r.a
for i >= 0 {
s := sStack[i]
p := pStack[2*i:]
if s > 0 {
// Split the quadratic curve p[:3] into an equivalent set of two
// shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2],
// and p[0] is unchanged.
mx := p[1].X
p[4].X = p[2].X
p[3].X = (p[4].X + mx) / 2
p[1].X = (p[0].X + mx) / 2
p[2].X = (p[1].X + p[3].X) / 2
my := p[1].Y
p[4].Y = p[2].Y
p[3].Y = (p[4].Y + my) / 2
p[1].Y = (p[0].Y + my) / 2
p[2].Y = (p[1].Y + p[3].Y) / 2
// The two shorter curves have one less split to do.
sStack[i] = s - 1
sStack[i+1] = s - 1
i++
} else {
// Replace the level-0 quadratic with a two-linear-piece
// approximation.
midx := (p[0].X + 2*p[1].X + p[2].X) / 4
midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4
r.Add1(fixed.Point26_6{midx, midy})
r.Add1(p[0])
i--
}
}
}
// Add3 adds a cubic segment to the current curve.
func (r *Rasterizer) Add3(b, c, d fixed.Point26_6) {
// Calculate nSplit (the number of recursive decompositions) based on how
// 'curvy' it is.
dev2 := maxAbs(r.a.X-3*(b.X+c.X)+d.X, r.a.Y-3*(b.Y+c.Y)+d.Y) / fixed.Int26_6(r.splitScale2)
dev3 := maxAbs(r.a.X-2*b.X+d.X, r.a.Y-2*b.Y+d.Y) / fixed.Int26_6(r.splitScale3)
nsplit := 0
for dev2 > 0 || dev3 > 0 {
dev2 /= 8
dev3 /= 4
nsplit++
}
// devN is 32-bit, and nsplit++ every time we shift off 2 bits, so
// maxNsplit is 16.
const maxNsplit = 16
if nsplit > maxNsplit {
panic("freetype/raster: Add3 nsplit too large: " + strconv.Itoa(nsplit))
}
// Recursively decompose the curve nSplit levels deep.
var (
pStack [3*maxNsplit + 4]fixed.Point26_6
sStack [maxNsplit + 1]int
i int
)
sStack[0] = nsplit
pStack[0] = d
pStack[1] = c
pStack[2] = b
pStack[3] = r.a
for i >= 0 {
s := sStack[i]
p := pStack[3*i:]
if s > 0 {
// Split the cubic curve p[:4] into an equivalent set of two
// shorter curves: p[:4] and p[3:7]. The new p[6] is the old p[3],
// and p[0] is unchanged.
m01x := (p[0].X + p[1].X) / 2
m12x := (p[1].X + p[2].X) / 2
m23x := (p[2].X + p[3].X) / 2
p[6].X = p[3].X
p[5].X = m23x
p[1].X = m01x
p[2].X = (m01x + m12x) / 2
p[4].X = (m12x + m23x) / 2
p[3].X = (p[2].X + p[4].X) / 2
m01y := (p[0].Y + p[1].Y) / 2
m12y := (p[1].Y + p[2].Y) / 2
m23y := (p[2].Y + p[3].Y) / 2
p[6].Y = p[3].Y
p[5].Y = m23y
p[1].Y = m01y
p[2].Y = (m01y + m12y) / 2
p[4].Y = (m12y + m23y) / 2
p[3].Y = (p[2].Y + p[4].Y) / 2
// The two shorter curves have one less split to do.
sStack[i] = s - 1
sStack[i+1] = s - 1
i++
} else {
// Replace the level-0 cubic with a two-linear-piece approximation.
midx := (p[0].X + 3*(p[1].X+p[2].X) + p[3].X) / 8
midy := (p[0].Y + 3*(p[1].Y+p[2].Y) + p[3].Y) / 8
r.Add1(fixed.Point26_6{midx, midy})
r.Add1(p[0])
i--
}
}
}
// AddPath adds the given Path.
func (r *Rasterizer) AddPath(p Path) {
for i := 0; i < len(p); {
switch p[i] {
case 0:
r.Start(
fixed.Point26_6{p[i+1], p[i+2]},
)
i += 4
case 1:
r.Add1(
fixed.Point26_6{p[i+1], p[i+2]},
)
i += 4
case 2:
r.Add2(
fixed.Point26_6{p[i+1], p[i+2]},
fixed.Point26_6{p[i+3], p[i+4]},
)
i += 6
case 3:
r.Add3(
fixed.Point26_6{p[i+1], p[i+2]},
fixed.Point26_6{p[i+3], p[i+4]},
fixed.Point26_6{p[i+5], p[i+6]},
)
i += 8
default:
panic("freetype/raster: bad path")
}
}
}
// AddStroke adds a stroked Path.
func (r *Rasterizer) AddStroke(q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
Stroke(r, q, width, cr, jr)
}
// areaToAlpha converts an area value to a uint32 alpha value. A completely
// filled pixel corresponds to an area of 64*64*2, and an alpha of 0xffff. The
// conversion of area values greater than this depends on the winding rule:
// even-odd or non-zero.
func (r *Rasterizer) areaToAlpha(area int) uint32 {
// The C Freetype implementation (version 2.3.12) does "alpha := area>>1"
// without the +1. Round-to-nearest gives a more symmetric result than
// round-down. The C implementation also returns 8-bit alpha, not 16-bit
// alpha.
a := (area + 1) >> 1
if a < 0 {
a = -a
}
alpha := uint32(a)
if r.UseNonZeroWinding {
if alpha > 0x0fff {
alpha = 0x0fff
}
} else {
alpha &= 0x1fff
if alpha > 0x1000 {
alpha = 0x2000 - alpha
} else if alpha == 0x1000 {
alpha = 0x0fff
}
}
// alpha is now in the range [0x0000, 0x0fff]. Convert that 12-bit alpha to
// 16-bit alpha.
return alpha<<4 | alpha>>8
}
// Rasterize converts r's accumulated curves into Spans for p. The Spans passed
// to p are non-overlapping, and sorted by Y and then X. They all have non-zero
// width (and 0 <= X0 < X1 <= r.width) and non-zero A, except for the final
// Span, which has Y, X0, X1 and A all equal to zero.
func (r *Rasterizer) Rasterize(p Painter) {
r.saveCell()
s := 0
for yi := 0; yi < len(r.cellIndex); yi++ {
xi, cover := 0, 0
for c := r.cellIndex[yi]; c != -1; c = r.cell[c].next {
if cover != 0 && r.cell[c].xi > xi {
alpha := r.areaToAlpha(cover * 64 * 2)
if alpha != 0 {
xi0, xi1 := xi, r.cell[c].xi
if xi0 < 0 {
xi0 = 0
}
if xi1 >= r.width {
xi1 = r.width
}
if xi0 < xi1 {
r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
s++
}
}
}
cover += r.cell[c].cover
alpha := r.areaToAlpha(cover*64*2 - r.cell[c].area)
xi = r.cell[c].xi + 1
if alpha != 0 {
xi0, xi1 := r.cell[c].xi, xi
if xi0 < 0 {
xi0 = 0
}
if xi1 >= r.width {
xi1 = r.width
}
if xi0 < xi1 {
r.spanBuf[s] = Span{yi + r.Dy, xi0 + r.Dx, xi1 + r.Dx, alpha}
s++
}
}
if s > len(r.spanBuf)-2 {
p.Paint(r.spanBuf[:s], false)
s = 0
}
}
}
p.Paint(r.spanBuf[:s], true)
}
// Clear cancels any previous calls to r.Start or r.AddXxx.
func (r *Rasterizer) Clear() {
r.a = fixed.Point26_6{}
r.xi = 0
r.yi = 0
r.area = 0
r.cover = 0
r.cell = r.cell[:0]
for i := 0; i < len(r.cellIndex); i++ {
r.cellIndex[i] = -1
}
}
// SetBounds sets the maximum width and height of the rasterized image and
// calls Clear. The width and height are in pixels, not fixed.Int26_6 units.
func (r *Rasterizer) SetBounds(width, height int) {
if width < 0 {
width = 0
}
if height < 0 {
height = 0
}
// Use the same ssN heuristic as the C Freetype (version 2.4.0)
// implementation.
ss2, ss3 := 32, 16
if width > 24 || height > 24 {
ss2, ss3 = 2*ss2, 2*ss3
if width > 120 || height > 120 {
ss2, ss3 = 2*ss2, 2*ss3
}
}
r.width = width
r.splitScale2 = ss2
r.splitScale3 = ss3
r.cell = r.cellBuf[:0]
if height > len(r.cellIndexBuf) {
r.cellIndex = make([]int, height)
} else {
r.cellIndex = r.cellIndexBuf[:height]
}
r.Clear()
}
// NewRasterizer creates a new Rasterizer with the given bounds.
func NewRasterizer(width, height int) *Rasterizer {
r := new(Rasterizer)
r.SetBounds(width, height)
return r
}

View file

@ -1,483 +0,0 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package raster
import (
"golang.org/x/image/math/fixed"
)
// Two points are considered practically equal if the square of the distance
// between them is less than one quarter (i.e. 1024 / 4096).
const epsilon = fixed.Int52_12(1024)
// A Capper signifies how to begin or end a stroked path.
type Capper interface {
// Cap adds a cap to p given a pivot point and the normal vector of a
// terminal segment. The normal's length is half of the stroke width.
Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6)
}
// The CapperFunc type adapts an ordinary function to be a Capper.
type CapperFunc func(Adder, fixed.Int26_6, fixed.Point26_6, fixed.Point26_6)
func (f CapperFunc) Cap(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
f(p, halfWidth, pivot, n1)
}
// A Joiner signifies how to join interior nodes of a stroked path.
type Joiner interface {
// Join adds a join to the two sides of a stroked path given a pivot
// point and the normal vectors of the trailing and leading segments.
// Both normals have length equal to half of the stroke width.
Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
}
// The JoinerFunc type adapts an ordinary function to be a Joiner.
type JoinerFunc func(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6)
func (f JoinerFunc) Join(lhs, rhs Adder, halfWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
f(lhs, rhs, halfWidth, pivot, n0, n1)
}
// RoundCapper adds round caps to a stroked path.
var RoundCapper Capper = CapperFunc(roundCapper)
func roundCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
// The cubic Bézier approximation to a circle involves the magic number
// (√2 - 1) * 4/3, which is approximately 35/64.
const k = 35
e := pRot90CCW(n1)
side := pivot.Add(e)
start, end := pivot.Sub(n1), pivot.Add(n1)
d, e := n1.Mul(k), e.Mul(k)
p.Add3(start.Add(e), side.Sub(d), side)
p.Add3(side.Add(d), end.Add(e), end)
}
// ButtCapper adds butt caps to a stroked path.
var ButtCapper Capper = CapperFunc(buttCapper)
func buttCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
p.Add1(pivot.Add(n1))
}
// SquareCapper adds square caps to a stroked path.
var SquareCapper Capper = CapperFunc(squareCapper)
func squareCapper(p Adder, halfWidth fixed.Int26_6, pivot, n1 fixed.Point26_6) {
e := pRot90CCW(n1)
side := pivot.Add(e)
p.Add1(side.Sub(n1))
p.Add1(side.Add(n1))
p.Add1(pivot.Add(n1))
}
// RoundJoiner adds round joins to a stroked path.
var RoundJoiner Joiner = JoinerFunc(roundJoiner)
func roundJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
dot := pDot(pRot90CW(n0), n1)
if dot >= 0 {
addArc(lhs, pivot, n0, n1)
rhs.Add1(pivot.Sub(n1))
} else {
lhs.Add1(pivot.Add(n1))
addArc(rhs, pivot, pNeg(n0), pNeg(n1))
}
}
// BevelJoiner adds bevel joins to a stroked path.
var BevelJoiner Joiner = JoinerFunc(bevelJoiner)
func bevelJoiner(lhs, rhs Adder, haflWidth fixed.Int26_6, pivot, n0, n1 fixed.Point26_6) {
lhs.Add1(pivot.Add(n1))
rhs.Add1(pivot.Sub(n1))
}
// addArc adds a circular arc from pivot+n0 to pivot+n1 to p. The shorter of
// the two possible arcs is taken, i.e. the one spanning <= 180 degrees. The
// two vectors n0 and n1 must be of equal length.
func addArc(p Adder, pivot, n0, n1 fixed.Point26_6) {
// r2 is the square of the length of n0.
r2 := pDot(n0, n0)
if r2 < epsilon {
// The arc radius is so small that we collapse to a straight line.
p.Add1(pivot.Add(n1))
return
}
// We approximate the arc by 0, 1, 2 or 3 45-degree quadratic segments plus
// a final quadratic segment from s to n1. Each 45-degree segment has
// control points {1, 0}, {1, tan(π/8)} and {1/√2, 1/√2} suitably scaled,
// rotated and translated. tan(π/8) is approximately 27/64.
const tpo8 = 27
var s fixed.Point26_6
// We determine which octant the angle between n0 and n1 is in via three
// dot products. m0, m1 and m2 are n0 rotated clockwise by 45, 90 and 135
// degrees.
m0 := pRot45CW(n0)
m1 := pRot90CW(n0)
m2 := pRot90CW(m0)
if pDot(m1, n1) >= 0 {
if pDot(n0, n1) >= 0 {
if pDot(m2, n1) <= 0 {
// n1 is between 0 and 45 degrees clockwise of n0.
s = n0
} else {
// n1 is between 45 and 90 degrees clockwise of n0.
p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0))
s = m0
}
} else {
pm1, n0t := pivot.Add(m1), n0.Mul(tpo8)
p.Add2(pivot.Add(n0).Add(m1.Mul(tpo8)), pivot.Add(m0))
p.Add2(pm1.Add(n0t), pm1)
if pDot(m0, n1) >= 0 {
// n1 is between 90 and 135 degrees clockwise of n0.
s = m1
} else {
// n1 is between 135 and 180 degrees clockwise of n0.
p.Add2(pm1.Sub(n0t), pivot.Add(m2))
s = m2
}
}
} else {
if pDot(n0, n1) >= 0 {
if pDot(m0, n1) >= 0 {
// n1 is between 0 and 45 degrees counter-clockwise of n0.
s = n0
} else {
// n1 is between 45 and 90 degrees counter-clockwise of n0.
p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2))
s = pNeg(m2)
}
} else {
pm1, n0t := pivot.Sub(m1), n0.Mul(tpo8)
p.Add2(pivot.Add(n0).Sub(m1.Mul(tpo8)), pivot.Sub(m2))
p.Add2(pm1.Add(n0t), pm1)
if pDot(m2, n1) <= 0 {
// n1 is between 90 and 135 degrees counter-clockwise of n0.
s = pNeg(m1)
} else {
// n1 is between 135 and 180 degrees counter-clockwise of n0.
p.Add2(pm1.Sub(n0t), pivot.Sub(m0))
s = pNeg(m0)
}
}
}
// The final quadratic segment has two endpoints s and n1 and the middle
// control point is a multiple of s.Add(n1), i.e. it is on the angle
// bisector of those two points. The multiple ranges between 128/256 and
// 150/256 as the angle between s and n1 ranges between 0 and 45 degrees.
//
// When the angle is 0 degrees (i.e. s and n1 are coincident) then
// s.Add(n1) is twice s and so the middle control point of the degenerate
// quadratic segment should be half s.Add(n1), and half = 128/256.
//
// When the angle is 45 degrees then 150/256 is the ratio of the lengths of
// the two vectors {1, tan(π/8)} and {1 + 1/√2, 1/√2}.
//
// d is the normalized dot product between s and n1. Since the angle ranges
// between 0 and 45 degrees then d ranges between 256/256 and 181/256.
d := 256 * pDot(s, n1) / r2
multiple := fixed.Int26_6(150-(150-128)*(d-181)/(256-181)) >> 2
p.Add2(pivot.Add(s.Add(n1).Mul(multiple)), pivot.Add(n1))
}
// midpoint returns the midpoint of two Points.
func midpoint(a, b fixed.Point26_6) fixed.Point26_6 {
return fixed.Point26_6{(a.X + b.X) / 2, (a.Y + b.Y) / 2}
}
// angleGreaterThan45 returns whether the angle between two vectors is more
// than 45 degrees.
func angleGreaterThan45(v0, v1 fixed.Point26_6) bool {
v := pRot45CCW(v0)
return pDot(v, v1) < 0 || pDot(pRot90CW(v), v1) < 0
}
// interpolate returns the point (1-t)*a + t*b.
func interpolate(a, b fixed.Point26_6, t fixed.Int52_12) fixed.Point26_6 {
s := 1<<12 - t
x := s*fixed.Int52_12(a.X) + t*fixed.Int52_12(b.X)
y := s*fixed.Int52_12(a.Y) + t*fixed.Int52_12(b.Y)
return fixed.Point26_6{fixed.Int26_6(x >> 12), fixed.Int26_6(y >> 12)}
}
// curviest2 returns the value of t for which the quadratic parametric curve
// (1-t)²*a + 2*t*(1-t).b + t²*c has maximum curvature.
//
// The curvature of the parametric curve f(t) = (x(t), y(t)) is
// |xy″-yx″| / (x²+y²)^(3/2).
//
// Let d = b-a and e = c-2*b+a, so that f(t) = 2*d+2*e*t and f″(t) = 2*e.
// The curvature's numerator is (2*dx+2*ex*t)*(2*ey)-(2*dy+2*ey*t)*(2*ex),
// which simplifies to 4*dx*ey-4*dy*ex, which is constant with respect to t.
//
// Thus, curvature is extreme where the denominator is extreme, i.e. where
// (x²+y²) is extreme. The first order condition is that
// 2*x*x″+2*y*y″ = 0, or (dx+ex*t)*ex + (dy+ey*t)*ey = 0.
// Solving for t gives t = -(dx*ex+dy*ey) / (ex*ex+ey*ey).
func curviest2(a, b, c fixed.Point26_6) fixed.Int52_12 {
dx := int64(b.X - a.X)
dy := int64(b.Y - a.Y)
ex := int64(c.X - 2*b.X + a.X)
ey := int64(c.Y - 2*b.Y + a.Y)
if ex == 0 && ey == 0 {
return 2048
}
return fixed.Int52_12(-4096 * (dx*ex + dy*ey) / (ex*ex + ey*ey))
}
// A stroker holds state for stroking a path.
type stroker struct {
// p is the destination that records the stroked path.
p Adder
// u is the half-width of the stroke.
u fixed.Int26_6
// cr and jr specify how to end and connect path segments.
cr Capper
jr Joiner
// r is the reverse path. Stroking a path involves constructing two
// parallel paths 2*u apart. The first path is added immediately to p,
// the second path is accumulated in r and eventually added in reverse.
r Path
// a is the most recent segment point. anorm is the segment normal of
// length u at that point.
a, anorm fixed.Point26_6
}
// addNonCurvy2 adds a quadratic segment to the stroker, where the segment
// defined by (k.a, b, c) achieves maximum curvature at either k.a or c.
func (k *stroker) addNonCurvy2(b, c fixed.Point26_6) {
// We repeatedly divide the segment at its middle until it is straight
// enough to approximate the stroke by just translating the control points.
// ds and ps are stacks of depths and points. t is the top of the stack.
const maxDepth = 5
var (
ds [maxDepth + 1]int
ps [2*maxDepth + 3]fixed.Point26_6
t int
)
// Initially the ps stack has one quadratic segment of depth zero.
ds[0] = 0
ps[2] = k.a
ps[1] = b
ps[0] = c
anorm := k.anorm
var cnorm fixed.Point26_6
for {
depth := ds[t]
a := ps[2*t+2]
b := ps[2*t+1]
c := ps[2*t+0]
ab := b.Sub(a)
bc := c.Sub(b)
abIsSmall := pDot(ab, ab) < fixed.Int52_12(1<<12)
bcIsSmall := pDot(bc, bc) < fixed.Int52_12(1<<12)
if abIsSmall && bcIsSmall {
// Approximate the segment by a circular arc.
cnorm = pRot90CCW(pNorm(bc, k.u))
mac := midpoint(a, c)
addArc(k.p, mac, anorm, cnorm)
addArc(&k.r, mac, pNeg(anorm), pNeg(cnorm))
} else if depth < maxDepth && angleGreaterThan45(ab, bc) {
// Divide the segment in two and push both halves on the stack.
mab := midpoint(a, b)
mbc := midpoint(b, c)
t++
ds[t+0] = depth + 1
ds[t-1] = depth + 1
ps[2*t+2] = a
ps[2*t+1] = mab
ps[2*t+0] = midpoint(mab, mbc)
ps[2*t-1] = mbc
continue
} else {
// Translate the control points.
bnorm := pRot90CCW(pNorm(c.Sub(a), k.u))
cnorm = pRot90CCW(pNorm(bc, k.u))
k.p.Add2(b.Add(bnorm), c.Add(cnorm))
k.r.Add2(b.Sub(bnorm), c.Sub(cnorm))
}
if t == 0 {
k.a, k.anorm = c, cnorm
return
}
t--
anorm = cnorm
}
panic("unreachable")
}
// Add1 adds a linear segment to the stroker.
func (k *stroker) Add1(b fixed.Point26_6) {
bnorm := pRot90CCW(pNorm(b.Sub(k.a), k.u))
if len(k.r) == 0 {
k.p.Start(k.a.Add(bnorm))
k.r.Start(k.a.Sub(bnorm))
} else {
k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, bnorm)
}
k.p.Add1(b.Add(bnorm))
k.r.Add1(b.Sub(bnorm))
k.a, k.anorm = b, bnorm
}
// Add2 adds a quadratic segment to the stroker.
func (k *stroker) Add2(b, c fixed.Point26_6) {
ab := b.Sub(k.a)
bc := c.Sub(b)
abnorm := pRot90CCW(pNorm(ab, k.u))
if len(k.r) == 0 {
k.p.Start(k.a.Add(abnorm))
k.r.Start(k.a.Sub(abnorm))
} else {
k.jr.Join(k.p, &k.r, k.u, k.a, k.anorm, abnorm)
}
// Approximate nearly-degenerate quadratics by linear segments.
abIsSmall := pDot(ab, ab) < epsilon
bcIsSmall := pDot(bc, bc) < epsilon
if abIsSmall || bcIsSmall {
acnorm := pRot90CCW(pNorm(c.Sub(k.a), k.u))
k.p.Add1(c.Add(acnorm))
k.r.Add1(c.Sub(acnorm))
k.a, k.anorm = c, acnorm
return
}
// The quadratic segment (k.a, b, c) has a point of maximum curvature.
// If this occurs at an end point, we process the segment as a whole.
t := curviest2(k.a, b, c)
if t <= 0 || 4096 <= t {
k.addNonCurvy2(b, c)
return
}
// Otherwise, we perform a de Casteljau decomposition at the point of
// maximum curvature and process the two straighter parts.
mab := interpolate(k.a, b, t)
mbc := interpolate(b, c, t)
mabc := interpolate(mab, mbc, t)
// If the vectors ab and bc are close to being in opposite directions,
// then the decomposition can become unstable, so we approximate the
// quadratic segment by two linear segments joined by an arc.
bcnorm := pRot90CCW(pNorm(bc, k.u))
if pDot(abnorm, bcnorm) < -fixed.Int52_12(k.u)*fixed.Int52_12(k.u)*2047/2048 {
pArc := pDot(abnorm, bc) < 0
k.p.Add1(mabc.Add(abnorm))
if pArc {
z := pRot90CW(abnorm)
addArc(k.p, mabc, abnorm, z)
addArc(k.p, mabc, z, bcnorm)
}
k.p.Add1(mabc.Add(bcnorm))
k.p.Add1(c.Add(bcnorm))
k.r.Add1(mabc.Sub(abnorm))
if !pArc {
z := pRot90CW(abnorm)
addArc(&k.r, mabc, pNeg(abnorm), z)
addArc(&k.r, mabc, z, pNeg(bcnorm))
}
k.r.Add1(mabc.Sub(bcnorm))
k.r.Add1(c.Sub(bcnorm))
k.a, k.anorm = c, bcnorm
return
}
// Process the decomposed parts.
k.addNonCurvy2(mab, mabc)
k.addNonCurvy2(mbc, c)
}
// Add3 adds a cubic segment to the stroker.
func (k *stroker) Add3(b, c, d fixed.Point26_6) {
panic("freetype/raster: stroke unimplemented for cubic segments")
}
// stroke adds the stroked Path q to p, where q consists of exactly one curve.
func (k *stroker) stroke(q Path) {
// Stroking is implemented by deriving two paths each k.u apart from q.
// The left-hand-side path is added immediately to k.p; the right-hand-side
// path is accumulated in k.r. Once we've finished adding the LHS to k.p,
// we add the RHS in reverse order.
k.r = make(Path, 0, len(q))
k.a = fixed.Point26_6{q[1], q[2]}
for i := 4; i < len(q); {
switch q[i] {
case 1:
k.Add1(
fixed.Point26_6{q[i+1], q[i+2]},
)
i += 4
case 2:
k.Add2(
fixed.Point26_6{q[i+1], q[i+2]},
fixed.Point26_6{q[i+3], q[i+4]},
)
i += 6
case 3:
k.Add3(
fixed.Point26_6{q[i+1], q[i+2]},
fixed.Point26_6{q[i+3], q[i+4]},
fixed.Point26_6{q[i+5], q[i+6]},
)
i += 8
default:
panic("freetype/raster: bad path")
}
}
if len(k.r) == 0 {
return
}
// TODO(nigeltao): if q is a closed curve then we should join the first and
// last segments instead of capping them.
k.cr.Cap(k.p, k.u, q.lastPoint(), pNeg(k.anorm))
addPathReversed(k.p, k.r)
pivot := q.firstPoint()
k.cr.Cap(k.p, k.u, pivot, pivot.Sub(fixed.Point26_6{k.r[1], k.r[2]}))
}
// Stroke adds q stroked with the given width to p. The result is typically
// self-intersecting and should be rasterized with UseNonZeroWinding.
// cr and jr may be nil, which defaults to a RoundCapper or RoundJoiner.
func Stroke(p Adder, q Path, width fixed.Int26_6, cr Capper, jr Joiner) {
if len(q) == 0 {
return
}
if cr == nil {
cr = RoundCapper
}
if jr == nil {
jr = RoundJoiner
}
if q[0] != 0 {
panic("freetype/raster: bad path")
}
s := stroker{p: p, u: width / 2, cr: cr, jr: jr}
i := 0
for j := 4; j < len(q); {
switch q[j] {
case 0:
s.stroke(q[i:j])
i, j = j, j+4
case 1:
j += 4
case 2:
j += 6
case 3:
j += 8
default:
panic("freetype/raster: bad path")
}
}
s.stroke(q[i:])
}

View file

@ -1,42 +0,0 @@
Luxi fonts copyright (c) 2001 by Bigelow & Holmes Inc. Luxi font
instruction code copyright (c) 2001 by URW++ GmbH. All Rights
Reserved. Luxi is a registered trademark of Bigelow & Holmes Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of these Fonts and associated documentation files (the "Font
Software"), to deal in the Font Software, including without
limitation the rights to use, copy, merge, publish, distribute,
sublicense, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software.
The Font Software may not be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may not
be modified nor may additional glyphs or characters be added to the
Fonts. This License becomes null and void when the Fonts or Font
Software have been modified.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
BIGELOW & HOLMES INC. OR URW++ GMBH. BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT,
INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT
SOFTWARE.
Except as contained in this notice, the names of Bigelow & Holmes
Inc. and URW++ GmbH. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Font Software without
prior written authorization from Bigelow & Holmes Inc. and URW++ GmbH.
For further information, contact:
info@urwpp.de
or
design@bigelowandholmes.com

View file

@ -1,507 +0,0 @@
// Copyright 2015 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package truetype
import (
"image"
"math"
"github.com/golang/freetype/raster"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
func powerOf2(i int) bool {
return i != 0 && (i&(i-1)) == 0
}
// Options are optional arguments to NewFace.
type Options struct {
// Size is the font size in points, as in "a 10 point font size".
//
// A zero value means to use a 12 point font size.
Size float64
// DPI is the dots-per-inch resolution.
//
// A zero value means to use 72 DPI.
DPI float64
// Hinting is how to quantize the glyph nodes.
//
// A zero value means to use no hinting.
Hinting font.Hinting
// GlyphCacheEntries is the number of entries in the glyph mask image
// cache.
//
// If non-zero, it must be a power of 2.
//
// A zero value means to use 512 entries.
GlyphCacheEntries int
// SubPixelsX is the number of sub-pixel locations a glyph's dot is
// quantized to, in the horizontal direction. For example, a value of 8
// means that the dot is quantized to 1/8th of a pixel. This quantization
// only affects the glyph mask image, not its bounding box or advance
// width. A higher value gives a more faithful glyph image, but reduces the
// effectiveness of the glyph cache.
//
// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive.
//
// A zero value means to use 4 sub-pixel locations.
SubPixelsX int
// SubPixelsY is the number of sub-pixel locations a glyph's dot is
// quantized to, in the vertical direction. For example, a value of 8
// means that the dot is quantized to 1/8th of a pixel. This quantization
// only affects the glyph mask image, not its bounding box or advance
// width. A higher value gives a more faithful glyph image, but reduces the
// effectiveness of the glyph cache.
//
// If non-zero, it must be a power of 2, and be between 1 and 64 inclusive.
//
// A zero value means to use 1 sub-pixel location.
SubPixelsY int
}
func (o *Options) size() float64 {
if o != nil && o.Size > 0 {
return o.Size
}
return 12
}
func (o *Options) dpi() float64 {
if o != nil && o.DPI > 0 {
return o.DPI
}
return 72
}
func (o *Options) hinting() font.Hinting {
if o != nil {
switch o.Hinting {
case font.HintingVertical, font.HintingFull:
// TODO: support vertical hinting.
return font.HintingFull
}
}
return font.HintingNone
}
func (o *Options) glyphCacheEntries() int {
if o != nil && powerOf2(o.GlyphCacheEntries) {
return o.GlyphCacheEntries
}
// 512 is 128 * 4 * 1, which lets us cache 128 glyphs at 4 * 1 subpixel
// locations in the X and Y direction.
return 512
}
func (o *Options) subPixelsX() (value uint32, halfQuantum, mask fixed.Int26_6) {
if o != nil {
switch o.SubPixelsX {
case 1, 2, 4, 8, 16, 32, 64:
return subPixels(o.SubPixelsX)
}
}
// This default value of 4 isn't based on anything scientific, merely as
// small a number as possible that looks almost as good as no quantization,
// or returning subPixels(64).
return subPixels(4)
}
func (o *Options) subPixelsY() (value uint32, halfQuantum, mask fixed.Int26_6) {
if o != nil {
switch o.SubPixelsX {
case 1, 2, 4, 8, 16, 32, 64:
return subPixels(o.SubPixelsX)
}
}
// This default value of 1 isn't based on anything scientific, merely that
// vertical sub-pixel glyph rendering is pretty rare. Baseline locations
// can usually afford to snap to the pixel grid, so the vertical direction
// doesn't have the deal with the horizontal's fractional advance widths.
return subPixels(1)
}
// subPixels returns q and the bias and mask that leads to q quantized
// sub-pixel locations per full pixel.
//
// For example, q == 4 leads to a bias of 8 and a mask of 0xfffffff0, or -16,
// because we want to round fractions of fixed.Int26_6 as:
// - 0 to 7 rounds to 0.
// - 8 to 23 rounds to 16.
// - 24 to 39 rounds to 32.
// - 40 to 55 rounds to 48.
// - 56 to 63 rounds to 64.
// which means to add 8 and then bitwise-and with -16, in two's complement
// representation.
//
// When q == 1, we want bias == 32 and mask == -64.
// When q == 2, we want bias == 16 and mask == -32.
// When q == 4, we want bias == 8 and mask == -16.
// ...
// When q == 64, we want bias == 0 and mask == -1. (The no-op case).
// The pattern is clear.
func subPixels(q int) (value uint32, bias, mask fixed.Int26_6) {
return uint32(q), 32 / fixed.Int26_6(q), -64 / fixed.Int26_6(q)
}
// glyphCacheEntry caches the arguments and return values of rasterize.
type glyphCacheEntry struct {
key glyphCacheKey
val glyphCacheVal
}
type glyphCacheKey struct {
index Index
fx, fy uint8
}
type glyphCacheVal struct {
advanceWidth fixed.Int26_6
offset image.Point
gw int
gh int
}
type indexCacheEntry struct {
rune rune
index Index
}
// NewFace returns a new font.Face for the given Font.
func NewFace(f *Font, opts *Options) font.Face {
a := &face{
f: f,
hinting: opts.hinting(),
scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)),
glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()),
}
a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX()
a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY()
// Fill the cache with invalid entries. Valid glyph cache entries have fx
// and fy in the range [0, 64). Valid index cache entries have rune >= 0.
for i := range a.glyphCache {
a.glyphCache[i].key.fy = 0xff
}
for i := range a.indexCache {
a.indexCache[i].rune = -1
}
// Set the rasterizer's bounds to be big enough to handle the largest glyph.
b := f.Bounds(a.scale)
xmin := +int(b.Min.X) >> 6
ymin := -int(b.Max.Y) >> 6
xmax := +int(b.Max.X+63) >> 6
ymax := -int(b.Min.Y-63) >> 6
a.maxw = xmax - xmin
a.maxh = ymax - ymin
a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache)))
a.r.SetBounds(a.maxw, a.maxh)
a.p = facePainter{a}
return a
}
type face struct {
f *Font
hinting font.Hinting
scale fixed.Int26_6
subPixelX uint32
subPixelBiasX fixed.Int26_6
subPixelMaskX fixed.Int26_6
subPixelY uint32
subPixelBiasY fixed.Int26_6
subPixelMaskY fixed.Int26_6
masks *image.Alpha
glyphCache []glyphCacheEntry
r raster.Rasterizer
p raster.Painter
paintOffset int
maxw int
maxh int
glyphBuf GlyphBuf
indexCache [indexCacheLen]indexCacheEntry
// TODO: clip rectangle?
}
const indexCacheLen = 256
func (a *face) index(r rune) Index {
const mask = indexCacheLen - 1
c := &a.indexCache[r&mask]
if c.rune == r {
return c.index
}
i := a.f.Index(r)
c.rune = r
c.index = i
return i
}
// Close satisfies the font.Face interface.
func (a *face) Close() error { return nil }
// Metrics satisfies the font.Face interface.
func (a *face) Metrics() font.Metrics {
scale := float64(a.scale)
fupe := float64(a.f.FUnitsPerEm())
return font.Metrics{
Height: a.scale,
Ascent: fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)),
Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)),
}
}
// Kern satisfies the font.Face interface.
func (a *face) Kern(r0, r1 rune) fixed.Int26_6 {
i0 := a.index(r0)
i1 := a.index(r1)
kern := a.f.Kern(a.scale, i0, i1)
if a.hinting != font.HintingNone {
kern = (kern + 32) &^ 63
}
return kern
}
// Glyph satisfies the font.Face interface.
func (a *face) Glyph(dot fixed.Point26_6, r rune) (
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
// Quantize to the sub-pixel granularity.
dotX := (dot.X + a.subPixelBiasX) & a.subPixelMaskX
dotY := (dot.Y + a.subPixelBiasY) & a.subPixelMaskY
// Split the coordinates into their integer and fractional parts.
ix, fx := int(dotX>>6), dotX&0x3f
iy, fy := int(dotY>>6), dotY&0x3f
index := a.index(r)
cIndex := uint32(index)
cIndex = cIndex*a.subPixelX - uint32(fx/a.subPixelMaskX)
cIndex = cIndex*a.subPixelY - uint32(fy/a.subPixelMaskY)
cIndex &= uint32(len(a.glyphCache) - 1)
a.paintOffset = a.maxh * int(cIndex)
k := glyphCacheKey{
index: index,
fx: uint8(fx),
fy: uint8(fy),
}
var v glyphCacheVal
if a.glyphCache[cIndex].key != k {
var ok bool
v, ok = a.rasterize(index, fx, fy)
if !ok {
return image.Rectangle{}, nil, image.Point{}, 0, false
}
a.glyphCache[cIndex] = glyphCacheEntry{k, v}
} else {
v = a.glyphCache[cIndex].val
}
dr.Min = image.Point{
X: ix + v.offset.X,
Y: iy + v.offset.Y,
}
dr.Max = image.Point{
X: dr.Min.X + v.gw,
Y: dr.Min.Y + v.gh,
}
return dr, a.masks, image.Point{Y: a.paintOffset}, v.advanceWidth, true
}
func (a *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil {
return fixed.Rectangle26_6{}, 0, false
}
xmin := +a.glyphBuf.Bounds.Min.X
ymin := -a.glyphBuf.Bounds.Max.Y
xmax := +a.glyphBuf.Bounds.Max.X
ymax := -a.glyphBuf.Bounds.Min.Y
if xmin > xmax || ymin > ymax {
return fixed.Rectangle26_6{}, 0, false
}
return fixed.Rectangle26_6{
Min: fixed.Point26_6{
X: xmin,
Y: ymin,
},
Max: fixed.Point26_6{
X: xmax,
Y: ymax,
},
}, a.glyphBuf.AdvanceWidth, true
}
func (a *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
if err := a.glyphBuf.Load(a.f, a.scale, a.index(r), a.hinting); err != nil {
return 0, false
}
return a.glyphBuf.AdvanceWidth, true
}
// rasterize returns the advance width, integer-pixel offset to render at, and
// the width and height of the given glyph at the given sub-pixel offsets.
//
// The 26.6 fixed point arguments fx and fy must be in the range [0, 1).
func (a *face) rasterize(index Index, fx, fy fixed.Int26_6) (v glyphCacheVal, ok bool) {
if err := a.glyphBuf.Load(a.f, a.scale, index, a.hinting); err != nil {
return glyphCacheVal{}, false
}
// Calculate the integer-pixel bounds for the glyph.
xmin := int(fx+a.glyphBuf.Bounds.Min.X) >> 6
ymin := int(fy-a.glyphBuf.Bounds.Max.Y) >> 6
xmax := int(fx+a.glyphBuf.Bounds.Max.X+0x3f) >> 6
ymax := int(fy-a.glyphBuf.Bounds.Min.Y+0x3f) >> 6
if xmin > xmax || ymin > ymax {
return glyphCacheVal{}, false
}
// A TrueType's glyph's nodes can have negative co-ordinates, but the
// rasterizer clips anything left of x=0 or above y=0. xmin and ymin are
// the pixel offsets, based on the font's FUnit metrics, that let a
// negative co-ordinate in TrueType space be non-negative in rasterizer
// space. xmin and ymin are typically <= 0.
fx -= fixed.Int26_6(xmin << 6)
fy -= fixed.Int26_6(ymin << 6)
// Rasterize the glyph's vectors.
a.r.Clear()
pixOffset := a.paintOffset * a.maxw
clear(a.masks.Pix[pixOffset : pixOffset+a.maxw*a.maxh])
e0 := 0
for _, e1 := range a.glyphBuf.Ends {
a.drawContour(a.glyphBuf.Points[e0:e1], fx, fy)
e0 = e1
}
a.r.Rasterize(a.p)
return glyphCacheVal{
a.glyphBuf.AdvanceWidth,
image.Point{xmin, ymin},
xmax - xmin,
ymax - ymin,
}, true
}
func clear(pix []byte) {
for i := range pix {
pix[i] = 0
}
}
// drawContour draws the given closed contour with the given offset.
func (a *face) drawContour(ps []Point, dx, dy fixed.Int26_6) {
if len(ps) == 0 {
return
}
// The low bit of each point's Flags value is whether the point is on the
// curve. Truetype fonts only have quadratic Bézier curves, not cubics.
// Thus, two consecutive off-curve points imply an on-curve point in the
// middle of those two.
//
// See http://chanae.walon.org/pub/ttf/ttf_glyphs.htm for more details.
// ps[0] is a truetype.Point measured in FUnits and positive Y going
// upwards. start is the same thing measured in fixed point units and
// positive Y going downwards, and offset by (dx, dy).
start := fixed.Point26_6{
X: dx + ps[0].X,
Y: dy - ps[0].Y,
}
var others []Point
if ps[0].Flags&0x01 != 0 {
others = ps[1:]
} else {
last := fixed.Point26_6{
X: dx + ps[len(ps)-1].X,
Y: dy - ps[len(ps)-1].Y,
}
if ps[len(ps)-1].Flags&0x01 != 0 {
start = last
others = ps[:len(ps)-1]
} else {
start = fixed.Point26_6{
X: (start.X + last.X) / 2,
Y: (start.Y + last.Y) / 2,
}
others = ps
}
}
a.r.Start(start)
q0, on0 := start, true
for _, p := range others {
q := fixed.Point26_6{
X: dx + p.X,
Y: dy - p.Y,
}
on := p.Flags&0x01 != 0
if on {
if on0 {
a.r.Add1(q)
} else {
a.r.Add2(q0, q)
}
} else {
if on0 {
// No-op.
} else {
mid := fixed.Point26_6{
X: (q0.X + q.X) / 2,
Y: (q0.Y + q.Y) / 2,
}
a.r.Add2(q0, mid)
}
}
q0, on0 = q, on
}
// Close the curve.
if on0 {
a.r.Add1(start)
} else {
a.r.Add2(q0, start)
}
}
// facePainter is like a raster.AlphaSrcPainter, with an additional Y offset
// (face.paintOffset) to the painted spans.
type facePainter struct {
a *face
}
func (p facePainter) Paint(ss []raster.Span, done bool) {
m := p.a.masks
b := m.Bounds()
b.Min.Y = p.a.paintOffset
b.Max.Y = p.a.paintOffset + p.a.maxh
for _, s := range ss {
s.Y += p.a.paintOffset
if s.Y < b.Min.Y {
continue
}
if s.Y >= b.Max.Y {
return
}
if s.X0 < b.Min.X {
s.X0 = b.Min.X
}
if s.X1 > b.Max.X {
s.X1 = b.Max.X
}
if s.X0 >= s.X1 {
continue
}
base := (s.Y-m.Rect.Min.Y)*m.Stride - m.Rect.Min.X
p := m.Pix[base+s.X0 : base+s.X1]
color := uint8(s.Alpha >> 8)
for i := range p {
p[i] = color
}
}
}

View file

@ -1,522 +0,0 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package truetype
import (
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
// TODO: implement VerticalHinting.
// A Point is a co-ordinate pair plus whether it is 'on' a contour or an 'off'
// control point.
type Point struct {
X, Y fixed.Int26_6
// The Flags' LSB means whether or not this Point is 'on' the contour.
// Other bits are reserved for internal use.
Flags uint32
}
// A GlyphBuf holds a glyph's contours. A GlyphBuf can be re-used to load a
// series of glyphs from a Font.
type GlyphBuf struct {
// AdvanceWidth is the glyph's advance width.
AdvanceWidth fixed.Int26_6
// Bounds is the glyph's bounding box.
Bounds fixed.Rectangle26_6
// Points contains all Points from all contours of the glyph. If hinting
// was used to load a glyph then Unhinted contains those Points before they
// were hinted, and InFontUnits contains those Points before they were
// hinted and scaled.
Points, Unhinted, InFontUnits []Point
// Ends is the point indexes of the end point of each contour. The length
// of Ends is the number of contours in the glyph. The i'th contour
// consists of points Points[Ends[i-1]:Ends[i]], where Ends[-1] is
// interpreted to mean zero.
Ends []int
font *Font
scale fixed.Int26_6
hinting font.Hinting
hinter hinter
// phantomPoints are the co-ordinates of the synthetic phantom points
// used for hinting and bounding box calculations.
phantomPoints [4]Point
// pp1x is the X co-ordinate of the first phantom point. The '1' is
// using 1-based indexing; pp1x is almost always phantomPoints[0].X.
// TODO: eliminate this and consistently use phantomPoints[0].X.
pp1x fixed.Int26_6
// metricsSet is whether the glyph's metrics have been set yet. For a
// compound glyph, a sub-glyph may override the outer glyph's metrics.
metricsSet bool
// tmp is a scratch buffer.
tmp []Point
}
// Flags for decoding a glyph's contours. These flags are documented at
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
const (
flagOnCurve = 1 << iota
flagXShortVector
flagYShortVector
flagRepeat
flagPositiveXShortVector
flagPositiveYShortVector
// The remaining flags are for internal use.
flagTouchedX
flagTouchedY
)
// The same flag bits (0x10 and 0x20) are overloaded to have two meanings,
// dependent on the value of the flag{X,Y}ShortVector bits.
const (
flagThisXIsSame = flagPositiveXShortVector
flagThisYIsSame = flagPositiveYShortVector
)
// Load loads a glyph's contours from a Font, overwriting any previously loaded
// contours for this GlyphBuf. scale is the number of 26.6 fixed point units in
// 1 em, i is the glyph index, and h is the hinting policy.
func (g *GlyphBuf) Load(f *Font, scale fixed.Int26_6, i Index, h font.Hinting) error {
g.Points = g.Points[:0]
g.Unhinted = g.Unhinted[:0]
g.InFontUnits = g.InFontUnits[:0]
g.Ends = g.Ends[:0]
g.font = f
g.hinting = h
g.scale = scale
g.pp1x = 0
g.phantomPoints = [4]Point{}
g.metricsSet = false
if h != font.HintingNone {
if err := g.hinter.init(f, scale); err != nil {
return err
}
}
if err := g.load(0, i, true); err != nil {
return err
}
// TODO: this selection of either g.pp1x or g.phantomPoints[0].X isn't ideal,
// and should be cleaned up once we have all the testScaling tests passing,
// plus additional tests for Freetype-Go's bounding boxes matching C Freetype's.
pp1x := g.pp1x
if h != font.HintingNone {
pp1x = g.phantomPoints[0].X
}
if pp1x != 0 {
for i := range g.Points {
g.Points[i].X -= pp1x
}
}
advanceWidth := g.phantomPoints[1].X - g.phantomPoints[0].X
if h != font.HintingNone {
if len(f.hdmx) >= 8 {
if n := u32(f.hdmx, 4); n > 3+uint32(i) {
for hdmx := f.hdmx[8:]; uint32(len(hdmx)) >= n; hdmx = hdmx[n:] {
if fixed.Int26_6(hdmx[0]) == scale>>6 {
advanceWidth = fixed.Int26_6(hdmx[2+i]) << 6
break
}
}
}
}
advanceWidth = (advanceWidth + 32) &^ 63
}
g.AdvanceWidth = advanceWidth
// Set g.Bounds to the 'control box', which is the bounding box of the
// Bézier curves' control points. This is easier to calculate, no smaller
// than and often equal to the tightest possible bounding box of the curves
// themselves. This approach is what C Freetype does. We can't just scale
// the nominal bounding box in the glyf data as the hinting process and
// phantom point adjustment may move points outside of that box.
if len(g.Points) == 0 {
g.Bounds = fixed.Rectangle26_6{}
} else {
p := g.Points[0]
g.Bounds.Min.X = p.X
g.Bounds.Max.X = p.X
g.Bounds.Min.Y = p.Y
g.Bounds.Max.Y = p.Y
for _, p := range g.Points[1:] {
if g.Bounds.Min.X > p.X {
g.Bounds.Min.X = p.X
} else if g.Bounds.Max.X < p.X {
g.Bounds.Max.X = p.X
}
if g.Bounds.Min.Y > p.Y {
g.Bounds.Min.Y = p.Y
} else if g.Bounds.Max.Y < p.Y {
g.Bounds.Max.Y = p.Y
}
}
// Snap the box to the grid, if hinting is on.
if h != font.HintingNone {
g.Bounds.Min.X &^= 63
g.Bounds.Min.Y &^= 63
g.Bounds.Max.X += 63
g.Bounds.Max.X &^= 63
g.Bounds.Max.Y += 63
g.Bounds.Max.Y &^= 63
}
}
return nil
}
func (g *GlyphBuf) load(recursion uint32, i Index, useMyMetrics bool) (err error) {
// The recursion limit here is arbitrary, but defends against malformed glyphs.
if recursion >= 32 {
return UnsupportedError("excessive compound glyph recursion")
}
// Find the relevant slice of g.font.glyf.
var g0, g1 uint32
if g.font.locaOffsetFormat == locaOffsetFormatShort {
g0 = 2 * uint32(u16(g.font.loca, 2*int(i)))
g1 = 2 * uint32(u16(g.font.loca, 2*int(i)+2))
} else {
g0 = u32(g.font.loca, 4*int(i))
g1 = u32(g.font.loca, 4*int(i)+4)
}
// Decode the contour count and nominal bounding box, from the first
// 10 bytes of the glyf data. boundsYMin and boundsXMax, at offsets 4
// and 6, are unused.
glyf, ne, boundsXMin, boundsYMax := []byte(nil), 0, fixed.Int26_6(0), fixed.Int26_6(0)
if g0+10 <= g1 {
glyf = g.font.glyf[g0:g1]
ne = int(int16(u16(glyf, 0)))
boundsXMin = fixed.Int26_6(int16(u16(glyf, 2)))
boundsYMax = fixed.Int26_6(int16(u16(glyf, 8)))
}
// Create the phantom points.
uhm, pp1x := g.font.unscaledHMetric(i), fixed.Int26_6(0)
uvm := g.font.unscaledVMetric(i, boundsYMax)
g.phantomPoints = [4]Point{
{X: boundsXMin - uhm.LeftSideBearing},
{X: boundsXMin - uhm.LeftSideBearing + uhm.AdvanceWidth},
{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing},
{X: uhm.AdvanceWidth / 2, Y: boundsYMax + uvm.TopSideBearing - uvm.AdvanceHeight},
}
if len(glyf) == 0 {
g.addPhantomsAndScale(len(g.Points), len(g.Points), true, true)
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
g.Points = g.Points[:len(g.Points)-4]
// TODO: also trim g.InFontUnits and g.Unhinted?
return nil
}
// Load and hint the contours.
if ne < 0 {
if ne != -1 {
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html says that
// "the values -2, -3, and so forth, are reserved for future use."
return UnsupportedError("negative number of contours")
}
pp1x = g.font.scale(g.scale * (boundsXMin - uhm.LeftSideBearing))
if err := g.loadCompound(recursion, uhm, i, glyf, useMyMetrics); err != nil {
return err
}
} else {
np0, ne0 := len(g.Points), len(g.Ends)
program := g.loadSimple(glyf, ne)
g.addPhantomsAndScale(np0, np0, true, true)
pp1x = g.Points[len(g.Points)-4].X
if g.hinting != font.HintingNone {
if len(program) != 0 {
err := g.hinter.run(
program,
g.Points[np0:],
g.Unhinted[np0:],
g.InFontUnits[np0:],
g.Ends[ne0:],
)
if err != nil {
return err
}
}
// Drop the four phantom points.
g.InFontUnits = g.InFontUnits[:len(g.InFontUnits)-4]
g.Unhinted = g.Unhinted[:len(g.Unhinted)-4]
}
if useMyMetrics {
copy(g.phantomPoints[:], g.Points[len(g.Points)-4:])
}
g.Points = g.Points[:len(g.Points)-4]
if np0 != 0 {
// The hinting program expects the []Ends values to be indexed
// relative to the inner glyph, not the outer glyph, so we delay
// adding np0 until after the hinting program (if any) has run.
for i := ne0; i < len(g.Ends); i++ {
g.Ends[i] += np0
}
}
}
if useMyMetrics && !g.metricsSet {
g.metricsSet = true
g.pp1x = pp1x
}
return nil
}
// loadOffset is the initial offset for loadSimple and loadCompound. The first
// 10 bytes are the number of contours and the bounding box.
const loadOffset = 10
func (g *GlyphBuf) loadSimple(glyf []byte, ne int) (program []byte) {
offset := loadOffset
for i := 0; i < ne; i++ {
g.Ends = append(g.Ends, 1+int(u16(glyf, offset)))
offset += 2
}
// Note the TrueType hinting instructions.
instrLen := int(u16(glyf, offset))
offset += 2
program = glyf[offset : offset+instrLen]
offset += instrLen
if ne == 0 {
return program
}
np0 := len(g.Points)
np1 := np0 + int(g.Ends[len(g.Ends)-1])
// Decode the flags.
for i := np0; i < np1; {
c := uint32(glyf[offset])
offset++
g.Points = append(g.Points, Point{Flags: c})
i++
if c&flagRepeat != 0 {
count := glyf[offset]
offset++
for ; count > 0; count-- {
g.Points = append(g.Points, Point{Flags: c})
i++
}
}
}
// Decode the co-ordinates.
var x int16
for i := np0; i < np1; i++ {
f := g.Points[i].Flags
if f&flagXShortVector != 0 {
dx := int16(glyf[offset])
offset++
if f&flagPositiveXShortVector == 0 {
x -= dx
} else {
x += dx
}
} else if f&flagThisXIsSame == 0 {
x += int16(u16(glyf, offset))
offset += 2
}
g.Points[i].X = fixed.Int26_6(x)
}
var y int16
for i := np0; i < np1; i++ {
f := g.Points[i].Flags
if f&flagYShortVector != 0 {
dy := int16(glyf[offset])
offset++
if f&flagPositiveYShortVector == 0 {
y -= dy
} else {
y += dy
}
} else if f&flagThisYIsSame == 0 {
y += int16(u16(glyf, offset))
offset += 2
}
g.Points[i].Y = fixed.Int26_6(y)
}
return program
}
func (g *GlyphBuf) loadCompound(recursion uint32, uhm HMetric, i Index,
glyf []byte, useMyMetrics bool) error {
// Flags for decoding a compound glyph. These flags are documented at
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html.
const (
flagArg1And2AreWords = 1 << iota
flagArgsAreXYValues
flagRoundXYToGrid
flagWeHaveAScale
flagUnused
flagMoreComponents
flagWeHaveAnXAndYScale
flagWeHaveATwoByTwo
flagWeHaveInstructions
flagUseMyMetrics
flagOverlapCompound
)
np0, ne0 := len(g.Points), len(g.Ends)
offset := loadOffset
for {
flags := u16(glyf, offset)
component := Index(u16(glyf, offset+2))
dx, dy, transform, hasTransform := fixed.Int26_6(0), fixed.Int26_6(0), [4]int16{}, false
if flags&flagArg1And2AreWords != 0 {
dx = fixed.Int26_6(int16(u16(glyf, offset+4)))
dy = fixed.Int26_6(int16(u16(glyf, offset+6)))
offset += 8
} else {
dx = fixed.Int26_6(int16(int8(glyf[offset+4])))
dy = fixed.Int26_6(int16(int8(glyf[offset+5])))
offset += 6
}
if flags&flagArgsAreXYValues == 0 {
return UnsupportedError("compound glyph transform vector")
}
if flags&(flagWeHaveAScale|flagWeHaveAnXAndYScale|flagWeHaveATwoByTwo) != 0 {
hasTransform = true
switch {
case flags&flagWeHaveAScale != 0:
transform[0] = int16(u16(glyf, offset+0))
transform[3] = transform[0]
offset += 2
case flags&flagWeHaveAnXAndYScale != 0:
transform[0] = int16(u16(glyf, offset+0))
transform[3] = int16(u16(glyf, offset+2))
offset += 4
case flags&flagWeHaveATwoByTwo != 0:
transform[0] = int16(u16(glyf, offset+0))
transform[1] = int16(u16(glyf, offset+2))
transform[2] = int16(u16(glyf, offset+4))
transform[3] = int16(u16(glyf, offset+6))
offset += 8
}
}
savedPP := g.phantomPoints
np0 := len(g.Points)
componentUMM := useMyMetrics && (flags&flagUseMyMetrics != 0)
if err := g.load(recursion+1, component, componentUMM); err != nil {
return err
}
if flags&flagUseMyMetrics == 0 {
g.phantomPoints = savedPP
}
if hasTransform {
for j := np0; j < len(g.Points); j++ {
p := &g.Points[j]
newX := 0 +
fixed.Int26_6((int64(p.X)*int64(transform[0])+1<<13)>>14) +
fixed.Int26_6((int64(p.Y)*int64(transform[2])+1<<13)>>14)
newY := 0 +
fixed.Int26_6((int64(p.X)*int64(transform[1])+1<<13)>>14) +
fixed.Int26_6((int64(p.Y)*int64(transform[3])+1<<13)>>14)
p.X, p.Y = newX, newY
}
}
dx = g.font.scale(g.scale * dx)
dy = g.font.scale(g.scale * dy)
if flags&flagRoundXYToGrid != 0 {
dx = (dx + 32) &^ 63
dy = (dy + 32) &^ 63
}
for j := np0; j < len(g.Points); j++ {
p := &g.Points[j]
p.X += dx
p.Y += dy
}
// TODO: also adjust g.InFontUnits and g.Unhinted?
if flags&flagMoreComponents == 0 {
break
}
}
instrLen := 0
if g.hinting != font.HintingNone && offset+2 <= len(glyf) {
instrLen = int(u16(glyf, offset))
offset += 2
}
g.addPhantomsAndScale(np0, len(g.Points), false, instrLen > 0)
points, ends := g.Points[np0:], g.Ends[ne0:]
g.Points = g.Points[:len(g.Points)-4]
for j := range points {
points[j].Flags &^= flagTouchedX | flagTouchedY
}
if instrLen == 0 {
if !g.metricsSet {
copy(g.phantomPoints[:], points[len(points)-4:])
}
return nil
}
// Hint the compound glyph.
program := glyf[offset : offset+instrLen]
// Temporarily adjust the ends to be relative to this compound glyph.
if np0 != 0 {
for i := range ends {
ends[i] -= np0
}
}
// Hinting instructions of a composite glyph completely refer to the
// (already) hinted subglyphs.
g.tmp = append(g.tmp[:0], points...)
if err := g.hinter.run(program, points, g.tmp, g.tmp, ends); err != nil {
return err
}
if np0 != 0 {
for i := range ends {
ends[i] += np0
}
}
if !g.metricsSet {
copy(g.phantomPoints[:], points[len(points)-4:])
}
return nil
}
func (g *GlyphBuf) addPhantomsAndScale(np0, np1 int, simple, adjust bool) {
// Add the four phantom points.
g.Points = append(g.Points, g.phantomPoints[:]...)
// Scale the points.
if simple && g.hinting != font.HintingNone {
g.InFontUnits = append(g.InFontUnits, g.Points[np1:]...)
}
for i := np1; i < len(g.Points); i++ {
p := &g.Points[i]
p.X = g.font.scale(g.scale * p.X)
p.Y = g.font.scale(g.scale * p.Y)
}
if g.hinting == font.HintingNone {
return
}
// Round the 1st phantom point to the grid, shifting all other points equally.
// Note that "all other points" starts from np0, not np1.
// TODO: delete this adjustment and the np0/np1 distinction, when
// we update the compatibility tests to C Freetype 2.5.3.
// See http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=05c786d990390a7ca18e62962641dac740bacb06
if adjust {
pp1x := g.Points[len(g.Points)-4].X
if dx := ((pp1x + 32) &^ 63) - pp1x; dx != 0 {
for i := np0; i < len(g.Points); i++ {
g.Points[i].X += dx
}
}
}
if simple {
g.Unhinted = append(g.Unhinted, g.Points[np1:]...)
}
// Round the 2nd and 4th phantom point to the grid.
p := &g.Points[len(g.Points)-3]
p.X = (p.X + 32) &^ 63
p = &g.Points[len(g.Points)-1]
p.Y = (p.Y + 32) &^ 63
}

File diff suppressed because it is too large Load diff

View file

@ -1,289 +0,0 @@
// Copyright 2012 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
package truetype
// The Truetype opcodes are summarized at
// https://developer.apple.com/fonts/TTRefMan/RM07/appendixA.html
const (
opSVTCA0 = 0x00 // Set freedom and projection Vectors To Coordinate Axis
opSVTCA1 = 0x01 // .
opSPVTCA0 = 0x02 // Set Projection Vector To Coordinate Axis
opSPVTCA1 = 0x03 // .
opSFVTCA0 = 0x04 // Set Freedom Vector to Coordinate Axis
opSFVTCA1 = 0x05 // .
opSPVTL0 = 0x06 // Set Projection Vector To Line
opSPVTL1 = 0x07 // .
opSFVTL0 = 0x08 // Set Freedom Vector To Line
opSFVTL1 = 0x09 // .
opSPVFS = 0x0a // Set Projection Vector From Stack
opSFVFS = 0x0b // Set Freedom Vector From Stack
opGPV = 0x0c // Get Projection Vector
opGFV = 0x0d // Get Freedom Vector
opSFVTPV = 0x0e // Set Freedom Vector To Projection Vector
opISECT = 0x0f // moves point p to the InterSECTion of two lines
opSRP0 = 0x10 // Set Reference Point 0
opSRP1 = 0x11 // Set Reference Point 1
opSRP2 = 0x12 // Set Reference Point 2
opSZP0 = 0x13 // Set Zone Pointer 0
opSZP1 = 0x14 // Set Zone Pointer 1
opSZP2 = 0x15 // Set Zone Pointer 2
opSZPS = 0x16 // Set Zone PointerS
opSLOOP = 0x17 // Set LOOP variable
opRTG = 0x18 // Round To Grid
opRTHG = 0x19 // Round To Half Grid
opSMD = 0x1a // Set Minimum Distance
opELSE = 0x1b // ELSE clause
opJMPR = 0x1c // JuMP Relative
opSCVTCI = 0x1d // Set Control Value Table Cut-In
opSSWCI = 0x1e // Set Single Width Cut-In
opSSW = 0x1f // Set Single Width
opDUP = 0x20 // DUPlicate top stack element
opPOP = 0x21 // POP top stack element
opCLEAR = 0x22 // CLEAR the stack
opSWAP = 0x23 // SWAP the top two elements on the stack
opDEPTH = 0x24 // DEPTH of the stack
opCINDEX = 0x25 // Copy the INDEXed element to the top of the stack
opMINDEX = 0x26 // Move the INDEXed element to the top of the stack
opALIGNPTS = 0x27 // ALIGN PoinTS
op_0x28 = 0x28 // deprecated
opUTP = 0x29 // UnTouch Point
opLOOPCALL = 0x2a // LOOP and CALL function
opCALL = 0x2b // CALL function
opFDEF = 0x2c // Function DEFinition
opENDF = 0x2d // END Function definition
opMDAP0 = 0x2e // Move Direct Absolute Point
opMDAP1 = 0x2f // .
opIUP0 = 0x30 // Interpolate Untouched Points through the outline
opIUP1 = 0x31 // .
opSHP0 = 0x32 // SHift Point using reference point
opSHP1 = 0x33 // .
opSHC0 = 0x34 // SHift Contour using reference point
opSHC1 = 0x35 // .
opSHZ0 = 0x36 // SHift Zone using reference point
opSHZ1 = 0x37 // .
opSHPIX = 0x38 // SHift point by a PIXel amount
opIP = 0x39 // Interpolate Point
opMSIRP0 = 0x3a // Move Stack Indirect Relative Point
opMSIRP1 = 0x3b // .
opALIGNRP = 0x3c // ALIGN to Reference Point
opRTDG = 0x3d // Round To Double Grid
opMIAP0 = 0x3e // Move Indirect Absolute Point
opMIAP1 = 0x3f // .
opNPUSHB = 0x40 // PUSH N Bytes
opNPUSHW = 0x41 // PUSH N Words
opWS = 0x42 // Write Store
opRS = 0x43 // Read Store
opWCVTP = 0x44 // Write Control Value Table in Pixel units
opRCVT = 0x45 // Read Control Value Table entry
opGC0 = 0x46 // Get Coordinate projected onto the projection vector
opGC1 = 0x47 // .
opSCFS = 0x48 // Sets Coordinate From the Stack using projection vector and freedom vector
opMD0 = 0x49 // Measure Distance
opMD1 = 0x4a // .
opMPPEM = 0x4b // Measure Pixels Per EM
opMPS = 0x4c // Measure Point Size
opFLIPON = 0x4d // set the auto FLIP Boolean to ON
opFLIPOFF = 0x4e // set the auto FLIP Boolean to OFF
opDEBUG = 0x4f // DEBUG call
opLT = 0x50 // Less Than
opLTEQ = 0x51 // Less Than or EQual
opGT = 0x52 // Greater Than
opGTEQ = 0x53 // Greater Than or EQual
opEQ = 0x54 // EQual
opNEQ = 0x55 // Not EQual
opODD = 0x56 // ODD
opEVEN = 0x57 // EVEN
opIF = 0x58 // IF test
opEIF = 0x59 // End IF
opAND = 0x5a // logical AND
opOR = 0x5b // logical OR
opNOT = 0x5c // logical NOT
opDELTAP1 = 0x5d // DELTA exception P1
opSDB = 0x5e // Set Delta Base in the graphics state
opSDS = 0x5f // Set Delta Shift in the graphics state
opADD = 0x60 // ADD
opSUB = 0x61 // SUBtract
opDIV = 0x62 // DIVide
opMUL = 0x63 // MULtiply
opABS = 0x64 // ABSolute value
opNEG = 0x65 // NEGate
opFLOOR = 0x66 // FLOOR
opCEILING = 0x67 // CEILING
opROUND00 = 0x68 // ROUND value
opROUND01 = 0x69 // .
opROUND10 = 0x6a // .
opROUND11 = 0x6b // .
opNROUND00 = 0x6c // No ROUNDing of value
opNROUND01 = 0x6d // .
opNROUND10 = 0x6e // .
opNROUND11 = 0x6f // .
opWCVTF = 0x70 // Write Control Value Table in Funits
opDELTAP2 = 0x71 // DELTA exception P2
opDELTAP3 = 0x72 // DELTA exception P3
opDELTAC1 = 0x73 // DELTA exception C1
opDELTAC2 = 0x74 // DELTA exception C2
opDELTAC3 = 0x75 // DELTA exception C3
opSROUND = 0x76 // Super ROUND
opS45ROUND = 0x77 // Super ROUND 45 degrees
opJROT = 0x78 // Jump Relative On True
opJROF = 0x79 // Jump Relative On False
opROFF = 0x7a // Round OFF
op_0x7b = 0x7b // deprecated
opRUTG = 0x7c // Round Up To Grid
opRDTG = 0x7d // Round Down To Grid
opSANGW = 0x7e // Set ANGle Weight
opAA = 0x7f // Adjust Angle
opFLIPPT = 0x80 // FLIP PoinT
opFLIPRGON = 0x81 // FLIP RanGe ON
opFLIPRGOFF = 0x82 // FLIP RanGe OFF
op_0x83 = 0x83 // deprecated
op_0x84 = 0x84 // deprecated
opSCANCTRL = 0x85 // SCAN conversion ConTRoL
opSDPVTL0 = 0x86 // Set Dual Projection Vector To Line
opSDPVTL1 = 0x87 // .
opGETINFO = 0x88 // GET INFOrmation
opIDEF = 0x89 // Instruction DEFinition
opROLL = 0x8a // ROLL the top three stack elements
opMAX = 0x8b // MAXimum of top two stack elements
opMIN = 0x8c // MINimum of top two stack elements
opSCANTYPE = 0x8d // SCANTYPE
opINSTCTRL = 0x8e // INSTRuction execution ConTRoL
op_0x8f = 0x8f
op_0x90 = 0x90
op_0x91 = 0x91
op_0x92 = 0x92
op_0x93 = 0x93
op_0x94 = 0x94
op_0x95 = 0x95
op_0x96 = 0x96
op_0x97 = 0x97
op_0x98 = 0x98
op_0x99 = 0x99
op_0x9a = 0x9a
op_0x9b = 0x9b
op_0x9c = 0x9c
op_0x9d = 0x9d
op_0x9e = 0x9e
op_0x9f = 0x9f
op_0xa0 = 0xa0
op_0xa1 = 0xa1
op_0xa2 = 0xa2
op_0xa3 = 0xa3
op_0xa4 = 0xa4
op_0xa5 = 0xa5
op_0xa6 = 0xa6
op_0xa7 = 0xa7
op_0xa8 = 0xa8
op_0xa9 = 0xa9
op_0xaa = 0xaa
op_0xab = 0xab
op_0xac = 0xac
op_0xad = 0xad
op_0xae = 0xae
op_0xaf = 0xaf
opPUSHB000 = 0xb0 // PUSH Bytes
opPUSHB001 = 0xb1 // .
opPUSHB010 = 0xb2 // .
opPUSHB011 = 0xb3 // .
opPUSHB100 = 0xb4 // .
opPUSHB101 = 0xb5 // .
opPUSHB110 = 0xb6 // .
opPUSHB111 = 0xb7 // .
opPUSHW000 = 0xb8 // PUSH Words
opPUSHW001 = 0xb9 // .
opPUSHW010 = 0xba // .
opPUSHW011 = 0xbb // .
opPUSHW100 = 0xbc // .
opPUSHW101 = 0xbd // .
opPUSHW110 = 0xbe // .
opPUSHW111 = 0xbf // .
opMDRP00000 = 0xc0 // Move Direct Relative Point
opMDRP00001 = 0xc1 // .
opMDRP00010 = 0xc2 // .
opMDRP00011 = 0xc3 // .
opMDRP00100 = 0xc4 // .
opMDRP00101 = 0xc5 // .
opMDRP00110 = 0xc6 // .
opMDRP00111 = 0xc7 // .
opMDRP01000 = 0xc8 // .
opMDRP01001 = 0xc9 // .
opMDRP01010 = 0xca // .
opMDRP01011 = 0xcb // .
opMDRP01100 = 0xcc // .
opMDRP01101 = 0xcd // .
opMDRP01110 = 0xce // .
opMDRP01111 = 0xcf // .
opMDRP10000 = 0xd0 // .
opMDRP10001 = 0xd1 // .
opMDRP10010 = 0xd2 // .
opMDRP10011 = 0xd3 // .
opMDRP10100 = 0xd4 // .
opMDRP10101 = 0xd5 // .
opMDRP10110 = 0xd6 // .
opMDRP10111 = 0xd7 // .
opMDRP11000 = 0xd8 // .
opMDRP11001 = 0xd9 // .
opMDRP11010 = 0xda // .
opMDRP11011 = 0xdb // .
opMDRP11100 = 0xdc // .
opMDRP11101 = 0xdd // .
opMDRP11110 = 0xde // .
opMDRP11111 = 0xdf // .
opMIRP00000 = 0xe0 // Move Indirect Relative Point
opMIRP00001 = 0xe1 // .
opMIRP00010 = 0xe2 // .
opMIRP00011 = 0xe3 // .
opMIRP00100 = 0xe4 // .
opMIRP00101 = 0xe5 // .
opMIRP00110 = 0xe6 // .
opMIRP00111 = 0xe7 // .
opMIRP01000 = 0xe8 // .
opMIRP01001 = 0xe9 // .
opMIRP01010 = 0xea // .
opMIRP01011 = 0xeb // .
opMIRP01100 = 0xec // .
opMIRP01101 = 0xed // .
opMIRP01110 = 0xee // .
opMIRP01111 = 0xef // .
opMIRP10000 = 0xf0 // .
opMIRP10001 = 0xf1 // .
opMIRP10010 = 0xf2 // .
opMIRP10011 = 0xf3 // .
opMIRP10100 = 0xf4 // .
opMIRP10101 = 0xf5 // .
opMIRP10110 = 0xf6 // .
opMIRP10111 = 0xf7 // .
opMIRP11000 = 0xf8 // .
opMIRP11001 = 0xf9 // .
opMIRP11010 = 0xfa // .
opMIRP11011 = 0xfb // .
opMIRP11100 = 0xfc // .
opMIRP11101 = 0xfd // .
opMIRP11110 = 0xfe // .
opMIRP11111 = 0xff // .
)
// popCount is the number of stack elements that each opcode pops.
var popCount = [256]uint8{
// 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 5, // 0x00 - 0x0f
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, // 0x10 - 0x1f
1, 1, 0, 2, 0, 1, 1, 2, 0, 1, 2, 1, 1, 0, 1, 1, // 0x20 - 0x2f
0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 2, 2, 0, 0, 2, 2, // 0x30 - 0x3f
0, 0, 2, 1, 2, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, // 0x40 - 0x4f
2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, // 0x50 - 0x5f
2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f
2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 1, 1, // 0x70 - 0x7f
0, 2, 2, 0, 0, 1, 2, 2, 1, 1, 3, 2, 2, 1, 2, 0, // 0x80 - 0x8f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xc0 - 0xcf
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xd0 - 0xdf
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xe0 - 0xef
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xf0 - 0xff
}

View file

@ -1,653 +0,0 @@
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
// Use of this source code is governed by your choice of either the
// FreeType License or the GNU General Public License version 2 (or
// any later version), both of which can be found in the LICENSE file.
// Package truetype provides a parser for the TTF and TTC file formats.
// Those formats are documented at http://developer.apple.com/fonts/TTRefMan/
// and http://www.microsoft.com/typography/otspec/
//
// Some of a font's methods provide lengths or co-ordinates, e.g. bounds, font
// metrics and control points. All these methods take a scale parameter, which
// is the number of pixels in 1 em, expressed as a 26.6 fixed point value. For
// example, if 1 em is 10 pixels then scale is fixed.I(10), which is equal to
// fixed.Int26_6(10 << 6).
//
// To measure a TrueType font in ideal FUnit space, use scale equal to
// font.FUnitsPerEm().
package truetype // import "github.com/golang/freetype/truetype"
import (
"fmt"
"golang.org/x/image/math/fixed"
)
// An Index is a Font's index of a rune.
type Index uint16
// A NameID identifies a name table entry.
//
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
type NameID uint16
const (
NameIDCopyright NameID = 0
NameIDFontFamily = 1
NameIDFontSubfamily = 2
NameIDUniqueSubfamilyID = 3
NameIDFontFullName = 4
NameIDNameTableVersion = 5
NameIDPostscriptName = 6
NameIDTrademarkNotice = 7
NameIDManufacturerName = 8
NameIDDesignerName = 9
NameIDFontDescription = 10
NameIDFontVendorURL = 11
NameIDFontDesignerURL = 12
NameIDFontLicense = 13
NameIDFontLicenseURL = 14
NameIDPreferredFamily = 16
NameIDPreferredSubfamily = 17
NameIDCompatibleName = 18
NameIDSampleText = 19
)
const (
// A 32-bit encoding consists of a most-significant 16-bit Platform ID and a
// least-significant 16-bit Platform Specific ID. The magic numbers are
// specified at https://www.microsoft.com/typography/otspec/name.htm
unicodeEncodingBMPOnly = 0x00000003 // PID = 0 (Unicode), PSID = 3 (Unicode 2.0 BMP Only)
unicodeEncodingFull = 0x00000004 // PID = 0 (Unicode), PSID = 4 (Unicode 2.0 Full Repertoire)
microsoftSymbolEncoding = 0x00030000 // PID = 3 (Microsoft), PSID = 0 (Symbol)
microsoftUCS2Encoding = 0x00030001 // PID = 3 (Microsoft), PSID = 1 (UCS-2)
microsoftUCS4Encoding = 0x0003000a // PID = 3 (Microsoft), PSID = 10 (UCS-4)
)
// An HMetric holds the horizontal metrics of a single glyph.
type HMetric struct {
AdvanceWidth, LeftSideBearing fixed.Int26_6
}
// A VMetric holds the vertical metrics of a single glyph.
type VMetric struct {
AdvanceHeight, TopSideBearing fixed.Int26_6
}
// A FormatError reports that the input is not a valid TrueType font.
type FormatError string
func (e FormatError) Error() string {
return "freetype: invalid TrueType format: " + string(e)
}
// An UnsupportedError reports that the input uses a valid but unimplemented
// TrueType feature.
type UnsupportedError string
func (e UnsupportedError) Error() string {
return "freetype: unsupported TrueType feature: " + string(e)
}
// u32 returns the big-endian uint32 at b[i:].
func u32(b []byte, i int) uint32 {
return uint32(b[i])<<24 | uint32(b[i+1])<<16 | uint32(b[i+2])<<8 | uint32(b[i+3])
}
// u16 returns the big-endian uint16 at b[i:].
func u16(b []byte, i int) uint16 {
return uint16(b[i])<<8 | uint16(b[i+1])
}
// readTable returns a slice of the TTF data given by a table's directory entry.
func readTable(ttf []byte, offsetLength []byte) ([]byte, error) {
offset := int(u32(offsetLength, 0))
if offset < 0 {
return nil, FormatError(fmt.Sprintf("offset too large: %d", uint32(offset)))
}
length := int(u32(offsetLength, 4))
if length < 0 {
return nil, FormatError(fmt.Sprintf("length too large: %d", uint32(length)))
}
end := offset + length
if end < 0 || end > len(ttf) {
return nil, FormatError(fmt.Sprintf("offset + length too large: %d", uint32(offset)+uint32(length)))
}
return ttf[offset:end], nil
}
// parseSubtables returns the offset and platformID of the best subtable in
// table, where best favors a Unicode cmap encoding, and failing that, a
// Microsoft cmap encoding. offset is the offset of the first subtable in
// table, and size is the size of each subtable.
//
// If pred is non-nil, then only subtables that satisfy that predicate will be
// considered.
func parseSubtables(table []byte, name string, offset, size int, pred func([]byte) bool) (
bestOffset int, bestPID uint32, retErr error) {
if len(table) < 4 {
return 0, 0, FormatError(name + " too short")
}
nSubtables := int(u16(table, 2))
if len(table) < size*nSubtables+offset {
return 0, 0, FormatError(name + " too short")
}
ok := false
for i := 0; i < nSubtables; i, offset = i+1, offset+size {
if pred != nil && !pred(table[offset:]) {
continue
}
// We read the 16-bit Platform ID and 16-bit Platform Specific ID as a single uint32.
// All values are big-endian.
pidPsid := u32(table, offset)
// We prefer the Unicode cmap encoding. Failing to find that, we fall
// back onto the Microsoft cmap encoding.
if pidPsid == unicodeEncodingBMPOnly || pidPsid == unicodeEncodingFull {
bestOffset, bestPID, ok = offset, pidPsid>>16, true
break
} else if pidPsid == microsoftSymbolEncoding ||
pidPsid == microsoftUCS2Encoding ||
pidPsid == microsoftUCS4Encoding {
bestOffset, bestPID, ok = offset, pidPsid>>16, true
// We don't break out of the for loop, so that Unicode can override Microsoft.
}
}
if !ok {
return 0, 0, UnsupportedError(name + " encoding")
}
return bestOffset, bestPID, nil
}
const (
locaOffsetFormatUnknown int = iota
locaOffsetFormatShort
locaOffsetFormatLong
)
// A cm holds a parsed cmap entry.
type cm struct {
start, end, delta, offset uint32
}
// A Font represents a Truetype font.
type Font struct {
// Tables sliced from the TTF data. The different tables are documented
// at http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
cmap, cvt, fpgm, glyf, hdmx, head, hhea, hmtx, kern, loca, maxp, name, os2, prep, vmtx []byte
cmapIndexes []byte
// Cached values derived from the raw ttf data.
cm []cm
locaOffsetFormat int
nGlyph, nHMetric, nKern int
fUnitsPerEm int32
ascent int32 // In FUnits.
descent int32 // In FUnits; typically negative.
bounds fixed.Rectangle26_6 // In FUnits.
// Values from the maxp section.
maxTwilightPoints, maxStorage, maxFunctionDefs, maxStackElements uint16
}
func (f *Font) parseCmap() error {
const (
cmapFormat4 = 4
cmapFormat12 = 12
languageIndependent = 0
)
offset, _, err := parseSubtables(f.cmap, "cmap", 4, 8, nil)
if err != nil {
return err
}
offset = int(u32(f.cmap, offset+4))
if offset <= 0 || offset > len(f.cmap) {
return FormatError("bad cmap offset")
}
cmapFormat := u16(f.cmap, offset)
switch cmapFormat {
case cmapFormat4:
language := u16(f.cmap, offset+4)
if language != languageIndependent {
return UnsupportedError(fmt.Sprintf("language: %d", language))
}
segCountX2 := int(u16(f.cmap, offset+6))
if segCountX2%2 == 1 {
return FormatError(fmt.Sprintf("bad segCountX2: %d", segCountX2))
}
segCount := segCountX2 / 2
offset += 14
f.cm = make([]cm, segCount)
for i := 0; i < segCount; i++ {
f.cm[i].end = uint32(u16(f.cmap, offset))
offset += 2
}
offset += 2
for i := 0; i < segCount; i++ {
f.cm[i].start = uint32(u16(f.cmap, offset))
offset += 2
}
for i := 0; i < segCount; i++ {
f.cm[i].delta = uint32(u16(f.cmap, offset))
offset += 2
}
for i := 0; i < segCount; i++ {
f.cm[i].offset = uint32(u16(f.cmap, offset))
offset += 2
}
f.cmapIndexes = f.cmap[offset:]
return nil
case cmapFormat12:
if u16(f.cmap, offset+2) != 0 {
return FormatError(fmt.Sprintf("cmap format: % x", f.cmap[offset:offset+4]))
}
length := u32(f.cmap, offset+4)
language := u32(f.cmap, offset+8)
if language != languageIndependent {
return UnsupportedError(fmt.Sprintf("language: %d", language))
}
nGroups := u32(f.cmap, offset+12)
if length != 12*nGroups+16 {
return FormatError("inconsistent cmap length")
}
offset += 16
f.cm = make([]cm, nGroups)
for i := uint32(0); i < nGroups; i++ {
f.cm[i].start = u32(f.cmap, offset+0)
f.cm[i].end = u32(f.cmap, offset+4)
f.cm[i].delta = u32(f.cmap, offset+8) - f.cm[i].start
offset += 12
}
return nil
}
return UnsupportedError(fmt.Sprintf("cmap format: %d", cmapFormat))
}
func (f *Font) parseHead() error {
if len(f.head) != 54 {
return FormatError(fmt.Sprintf("bad head length: %d", len(f.head)))
}
f.fUnitsPerEm = int32(u16(f.head, 18))
f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36)))
f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38)))
f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40)))
f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42)))
switch i := u16(f.head, 50); i {
case 0:
f.locaOffsetFormat = locaOffsetFormatShort
case 1:
f.locaOffsetFormat = locaOffsetFormatLong
default:
return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i))
}
return nil
}
func (f *Font) parseHhea() error {
if len(f.hhea) != 36 {
return FormatError(fmt.Sprintf("bad hhea length: %d", len(f.hhea)))
}
f.ascent = int32(int16(u16(f.hhea, 4)))
f.descent = int32(int16(u16(f.hhea, 6)))
f.nHMetric = int(u16(f.hhea, 34))
if 4*f.nHMetric+2*(f.nGlyph-f.nHMetric) != len(f.hmtx) {
return FormatError(fmt.Sprintf("bad hmtx length: %d", len(f.hmtx)))
}
return nil
}
func (f *Font) parseKern() error {
// Apple's TrueType documentation (http://developer.apple.com/fonts/TTRefMan/RM06/Chap6kern.html) says:
// "Previous versions of the 'kern' table defined both the version and nTables fields in the header
// as UInt16 values and not UInt32 values. Use of the older format on the Mac OS is discouraged
// (although AAT can sense an old kerning table and still make correct use of it). Microsoft
// Windows still uses the older format for the 'kern' table and will not recognize the newer one.
// Fonts targeted for the Mac OS only should use the new format; fonts targeted for both the Mac OS
// and Windows should use the old format."
// Since we expect that almost all fonts aim to be Windows-compatible, we only parse the "older" format,
// just like the C Freetype implementation.
if len(f.kern) == 0 {
if f.nKern != 0 {
return FormatError("bad kern table length")
}
return nil
}
if len(f.kern) < 18 {
return FormatError("kern data too short")
}
version, offset := u16(f.kern, 0), 2
if version != 0 {
return UnsupportedError(fmt.Sprintf("kern version: %d", version))
}
n, offset := u16(f.kern, offset), offset+2
if n == 0 {
return UnsupportedError("kern nTables: 0")
}
// TODO: support multiple subtables. In practice, almost all .ttf files
// have only one subtable, if they have a kern table at all. But it's not
// impossible. Xolonium Regular (https://fontlibrary.org/en/font/xolonium)
// has 3 subtables. Those subtables appear to be disjoint, rather than
// being the same kerning pairs encoded in three different ways.
//
// For now, we'll use only the first subtable.
offset += 2 // Skip the version.
length, offset := int(u16(f.kern, offset)), offset+2
coverage, offset := u16(f.kern, offset), offset+2
if coverage != 0x0001 {
// We only support horizontal kerning.
return UnsupportedError(fmt.Sprintf("kern coverage: 0x%04x", coverage))
}
f.nKern, offset = int(u16(f.kern, offset)), offset+2
if 6*f.nKern != length-14 {
return FormatError("bad kern table length")
}
return nil
}
func (f *Font) parseMaxp() error {
if len(f.maxp) != 32 {
return FormatError(fmt.Sprintf("bad maxp length: %d", len(f.maxp)))
}
f.nGlyph = int(u16(f.maxp, 4))
f.maxTwilightPoints = u16(f.maxp, 16)
f.maxStorage = u16(f.maxp, 18)
f.maxFunctionDefs = u16(f.maxp, 20)
f.maxStackElements = u16(f.maxp, 24)
return nil
}
// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer.
func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 {
if x >= 0 {
x += fixed.Int26_6(f.fUnitsPerEm) / 2
} else {
x -= fixed.Int26_6(f.fUnitsPerEm) / 2
}
return x / fixed.Int26_6(f.fUnitsPerEm)
}
// Bounds returns the union of a Font's glyphs' bounds.
func (f *Font) Bounds(scale fixed.Int26_6) fixed.Rectangle26_6 {
b := f.bounds
b.Min.X = f.scale(scale * b.Min.X)
b.Min.Y = f.scale(scale * b.Min.Y)
b.Max.X = f.scale(scale * b.Max.X)
b.Max.Y = f.scale(scale * b.Max.Y)
return b
}
// FUnitsPerEm returns the number of FUnits in a Font's em-square's side.
func (f *Font) FUnitsPerEm() int32 {
return f.fUnitsPerEm
}
// Index returns a Font's index for the given rune.
func (f *Font) Index(x rune) Index {
c := uint32(x)
for i, j := 0, len(f.cm); i < j; {
h := i + (j-i)/2
cm := &f.cm[h]
if c < cm.start {
j = h
} else if cm.end < c {
i = h + 1
} else if cm.offset == 0 {
return Index(c + cm.delta)
} else {
offset := int(cm.offset) + 2*(h-len(f.cm)+int(c-cm.start))
return Index(u16(f.cmapIndexes, offset))
}
}
return 0
}
// Name returns the Font's name value for the given NameID. It returns "" if
// there was an error, or if that name was not found.
func (f *Font) Name(id NameID) string {
x, platformID, err := parseSubtables(f.name, "name", 6, 12, func(b []byte) bool {
return NameID(u16(b, 6)) == id
})
if err != nil {
return ""
}
offset, length := u16(f.name, 4)+u16(f.name, x+10), u16(f.name, x+8)
// Return the ASCII value of the encoded string.
// The string is encoded as UTF-16 on non-Apple platformIDs; Apple is platformID 1.
src := f.name[offset : offset+length]
var dst []byte
if platformID != 1 { // UTF-16.
if len(src)&1 != 0 {
return ""
}
dst = make([]byte, len(src)/2)
for i := range dst {
dst[i] = printable(u16(src, 2*i))
}
} else { // ASCII.
dst = make([]byte, len(src))
for i, c := range src {
dst[i] = printable(uint16(c))
}
}
return string(dst)
}
func printable(r uint16) byte {
if 0x20 <= r && r < 0x7f {
return byte(r)
}
return '?'
}
// unscaledHMetric returns the unscaled horizontal metrics for the glyph with
// the given index.
func (f *Font) unscaledHMetric(i Index) (h HMetric) {
j := int(i)
if j < 0 || f.nGlyph <= j {
return HMetric{}
}
if j >= f.nHMetric {
p := 4 * (f.nHMetric - 1)
return HMetric{
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)),
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))),
}
}
return HMetric{
AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)),
LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))),
}
}
// HMetric returns the horizontal metrics for the glyph with the given index.
func (f *Font) HMetric(scale fixed.Int26_6, i Index) HMetric {
h := f.unscaledHMetric(i)
h.AdvanceWidth = f.scale(scale * h.AdvanceWidth)
h.LeftSideBearing = f.scale(scale * h.LeftSideBearing)
return h
}
// unscaledVMetric returns the unscaled vertical metrics for the glyph with
// the given index. yMax is the top of the glyph's bounding box.
func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) {
j := int(i)
if j < 0 || f.nGlyph <= j {
return VMetric{}
}
if 4*j+4 <= len(f.vmtx) {
return VMetric{
AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)),
TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))),
}
}
// The OS/2 table has grown over time.
// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html
// says that it was originally 68 bytes. Optional fields, including
// the ascender and descender, are described at
// http://www.microsoft.com/typography/otspec/os2.htm
if len(f.os2) >= 72 {
sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68)))
sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70)))
return VMetric{
AdvanceHeight: sTypoAscender - sTypoDescender,
TopSideBearing: sTypoAscender - yMax,
}
}
return VMetric{
AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm),
TopSideBearing: 0,
}
}
// VMetric returns the vertical metrics for the glyph with the given index.
func (f *Font) VMetric(scale fixed.Int26_6, i Index) VMetric {
// TODO: should 0 be bounds.YMax?
v := f.unscaledVMetric(i, 0)
v.AdvanceHeight = f.scale(scale * v.AdvanceHeight)
v.TopSideBearing = f.scale(scale * v.TopSideBearing)
return v
}
// Kern returns the horizontal adjustment for the given glyph pair. A positive
// kern means to move the glyphs further apart.
func (f *Font) Kern(scale fixed.Int26_6, i0, i1 Index) fixed.Int26_6 {
if f.nKern == 0 {
return 0
}
g := uint32(i0)<<16 | uint32(i1)
lo, hi := 0, f.nKern
for lo < hi {
i := (lo + hi) / 2
ig := u32(f.kern, 18+6*i)
if ig < g {
lo = i + 1
} else if ig > g {
hi = i
} else {
return f.scale(scale * fixed.Int26_6(int16(u16(f.kern, 22+6*i))))
}
}
return 0
}
// Parse returns a new Font for the given TTF or TTC data.
//
// For TrueType Collections, the first font in the collection is parsed.
func Parse(ttf []byte) (font *Font, err error) {
return parse(ttf, 0)
}
func parse(ttf []byte, offset int) (font *Font, err error) {
if len(ttf)-offset < 12 {
err = FormatError("TTF data is too short")
return
}
originalOffset := offset
magic, offset := u32(ttf, offset), offset+4
switch magic {
case 0x00010000:
// No-op.
case 0x74746366: // "ttcf" as a big-endian uint32.
if originalOffset != 0 {
err = FormatError("recursive TTC")
return
}
ttcVersion, offset := u32(ttf, offset), offset+4
if ttcVersion != 0x00010000 && ttcVersion != 0x00020000 {
err = FormatError("bad TTC version")
return
}
numFonts, offset := int(u32(ttf, offset)), offset+4
if numFonts <= 0 {
err = FormatError("bad number of TTC fonts")
return
}
if len(ttf[offset:])/4 < numFonts {
err = FormatError("TTC offset table is too short")
return
}
// TODO: provide an API to select which font in a TrueType collection to return,
// not just the first one. This may require an API to parse a TTC's name tables,
// so users of this package can select the font in a TTC by name.
offset = int(u32(ttf, offset))
if offset <= 0 || offset > len(ttf) {
err = FormatError("bad TTC offset")
return
}
return parse(ttf, offset)
default:
err = FormatError("bad TTF version")
return
}
n, offset := int(u16(ttf, offset)), offset+2
offset += 6 // Skip the searchRange, entrySelector and rangeShift.
if len(ttf) < 16*n+offset {
err = FormatError("TTF data is too short")
return
}
f := new(Font)
// Assign the table slices.
for i := 0; i < n; i++ {
x := 16*i + offset
switch string(ttf[x : x+4]) {
case "cmap":
f.cmap, err = readTable(ttf, ttf[x+8:x+16])
case "cvt ":
f.cvt, err = readTable(ttf, ttf[x+8:x+16])
case "fpgm":
f.fpgm, err = readTable(ttf, ttf[x+8:x+16])
case "glyf":
f.glyf, err = readTable(ttf, ttf[x+8:x+16])
case "hdmx":
f.hdmx, err = readTable(ttf, ttf[x+8:x+16])
case "head":
f.head, err = readTable(ttf, ttf[x+8:x+16])
case "hhea":
f.hhea, err = readTable(ttf, ttf[x+8:x+16])
case "hmtx":
f.hmtx, err = readTable(ttf, ttf[x+8:x+16])
case "kern":
f.kern, err = readTable(ttf, ttf[x+8:x+16])
case "loca":
f.loca, err = readTable(ttf, ttf[x+8:x+16])
case "maxp":
f.maxp, err = readTable(ttf, ttf[x+8:x+16])
case "name":
f.name, err = readTable(ttf, ttf[x+8:x+16])
case "OS/2":
f.os2, err = readTable(ttf, ttf[x+8:x+16])
case "prep":
f.prep, err = readTable(ttf, ttf[x+8:x+16])
case "vmtx":
f.vmtx, err = readTable(ttf, ttf[x+8:x+16])
}
if err != nil {
return
}
}
// Parse and sanity-check the TTF data.
if err = f.parseHead(); err != nil {
return
}
if err = f.parseMaxp(); err != nil {
return
}
if err = f.parseCmap(); err != nil {
return
}
if err = f.parseKern(); err != nil {
return
}
if err = f.parseHhea(); err != nil {
return
}
font = f
return
}

202
vendor/github.com/golang/geo/LICENSE generated vendored
View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View file

@ -1,20 +0,0 @@
// Copyright 2014 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 r1 implements types and functions for working with geometry in ¹.
See ../s2 for a more detailed overview.
*/
package r1

View file

@ -1,159 +0,0 @@
// Copyright 2014 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 r1
import (
"fmt"
"math"
)
// Interval represents a closed interval on .
// Zero-length intervals (where Lo == Hi) represent single points.
// If Lo > Hi then the interval is empty.
type Interval struct {
Lo, Hi float64
}
// EmptyInterval returns an empty interval.
func EmptyInterval() Interval { return Interval{1, 0} }
// IntervalFromPoint returns an interval representing a single point.
func IntervalFromPoint(p float64) Interval { return Interval{p, p} }
// IsEmpty reports whether the interval is empty.
func (i Interval) IsEmpty() bool { return i.Lo > i.Hi }
// Equal returns true iff the interval contains the same points as oi.
func (i Interval) Equal(oi Interval) bool {
return i == oi || i.IsEmpty() && oi.IsEmpty()
}
// Center returns the midpoint of the interval.
// It is undefined for empty intervals.
func (i Interval) Center() float64 { return 0.5 * (i.Lo + i.Hi) }
// Length returns the length of the interval.
// The length of an empty interval is negative.
func (i Interval) Length() float64 { return i.Hi - i.Lo }
// Contains returns true iff the interval contains p.
func (i Interval) Contains(p float64) bool { return i.Lo <= p && p <= i.Hi }
// ContainsInterval returns true iff the interval contains oi.
func (i Interval) ContainsInterval(oi Interval) bool {
if oi.IsEmpty() {
return true
}
return i.Lo <= oi.Lo && oi.Hi <= i.Hi
}
// InteriorContains returns true iff the the interval strictly contains p.
func (i Interval) InteriorContains(p float64) bool {
return i.Lo < p && p < i.Hi
}
// InteriorContainsInterval returns true iff the interval strictly contains oi.
func (i Interval) InteriorContainsInterval(oi Interval) bool {
if oi.IsEmpty() {
return true
}
return i.Lo < oi.Lo && oi.Hi < i.Hi
}
// Intersects returns true iff the interval contains any points in common with oi.
func (i Interval) Intersects(oi Interval) bool {
if i.Lo <= oi.Lo {
return oi.Lo <= i.Hi && oi.Lo <= oi.Hi // oi.Lo ∈ i and oi is not empty
}
return i.Lo <= oi.Hi && i.Lo <= i.Hi // i.Lo ∈ oi and i is not empty
}
// InteriorIntersects returns true iff the interior of the interval contains any points in common with oi, including the latter's boundary.
func (i Interval) InteriorIntersects(oi Interval) bool {
return oi.Lo < i.Hi && i.Lo < oi.Hi && i.Lo < i.Hi && oi.Lo <= oi.Hi
}
// Intersection returns the interval containing all points common to i and j.
func (i Interval) Intersection(j Interval) Interval {
// Empty intervals do not need to be special-cased.
return Interval{
Lo: math.Max(i.Lo, j.Lo),
Hi: math.Min(i.Hi, j.Hi),
}
}
// AddPoint returns the interval expanded so that it contains the given point.
func (i Interval) AddPoint(p float64) Interval {
if i.IsEmpty() {
return Interval{p, p}
}
if p < i.Lo {
return Interval{p, i.Hi}
}
if p > i.Hi {
return Interval{i.Lo, p}
}
return i
}
// ClampPoint returns the closest point in the interval to the given point "p".
// The interval must be non-empty.
func (i Interval) ClampPoint(p float64) float64 {
return math.Max(i.Lo, math.Min(i.Hi, p))
}
// Expanded returns an interval that has been expanded on each side by margin.
// If margin is negative, then the function shrinks the interval on
// each side by margin instead. The resulting interval may be empty. Any
// expansion of an empty interval remains empty.
func (i Interval) Expanded(margin float64) Interval {
if i.IsEmpty() {
return i
}
return Interval{i.Lo - margin, i.Hi + margin}
}
// Union returns the smallest interval that contains this interval and the given interval.
func (i Interval) Union(other Interval) Interval {
if i.IsEmpty() {
return other
}
if other.IsEmpty() {
return i
}
return Interval{math.Min(i.Lo, other.Lo), math.Max(i.Hi, other.Hi)}
}
func (i Interval) String() string { return fmt.Sprintf("[%.7f, %.7f]", i.Lo, i.Hi) }
// epsilon is a small number that represents a reasonable level of noise between two
// values that can be considered to be equal.
const epsilon = 1e-14
// ApproxEqual reports whether the interval can be transformed into the
// given interval by moving each endpoint a small distance.
// The empty interval is considered to be positioned arbitrarily on the
// real line, so any interval with a small enough length will match
// the empty interval.
func (i Interval) ApproxEqual(other Interval) bool {
if i.IsEmpty() {
return other.Length() <= 2*epsilon
}
if other.IsEmpty() {
return i.Length() <= 2*epsilon
}
return math.Abs(other.Lo-i.Lo) <= epsilon &&
math.Abs(other.Hi-i.Hi) <= epsilon
}

View file

@ -1,20 +0,0 @@
// Copyright 2014 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 r2 implements types and functions for working with geometry in ².
See package s2 for a more detailed overview.
*/
package r2

View file

@ -1,255 +0,0 @@
// Copyright 2014 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 r2
import (
"fmt"
"math"
"github.com/golang/geo/r1"
)
// Point represents a point in ℝ².
type Point struct {
X, Y float64
}
// Add returns the sum of p and op.
func (p Point) Add(op Point) Point { return Point{p.X + op.X, p.Y + op.Y} }
// Sub returns the difference of p and op.
func (p Point) Sub(op Point) Point { return Point{p.X - op.X, p.Y - op.Y} }
// Mul returns the scalar product of p and m.
func (p Point) Mul(m float64) Point { return Point{m * p.X, m * p.Y} }
// Ortho returns a counterclockwise orthogonal point with the same norm.
func (p Point) Ortho() Point { return Point{-p.Y, p.X} }
// Dot returns the dot product between p and op.
func (p Point) Dot(op Point) float64 { return p.X*op.X + p.Y*op.Y }
// Cross returns the cross product of p and op.
func (p Point) Cross(op Point) float64 { return p.X*op.Y - p.Y*op.X }
// Norm returns the vector's norm.
func (p Point) Norm() float64 { return math.Hypot(p.X, p.Y) }
// Normalize returns a unit point in the same direction as p.
func (p Point) Normalize() Point {
if p.X == 0 && p.Y == 0 {
return p
}
return p.Mul(1 / p.Norm())
}
func (p Point) String() string { return fmt.Sprintf("(%.12f, %.12f)", p.X, p.Y) }
// Rect represents a closed axis-aligned rectangle in the (x,y) plane.
type Rect struct {
X, Y r1.Interval
}
// RectFromPoints constructs a rect that contains the given points.
func RectFromPoints(pts ...Point) Rect {
// Because the default value on interval is 0,0, we need to manually
// define the interval from the first point passed in as our starting
// interval, otherwise we end up with the case of passing in
// Point{0.2, 0.3} and getting the starting Rect of {0, 0.2}, {0, 0.3}
// instead of the Rect {0.2, 0.2}, {0.3, 0.3} which is not correct.
if len(pts) == 0 {
return Rect{}
}
r := Rect{
X: r1.Interval{Lo: pts[0].X, Hi: pts[0].X},
Y: r1.Interval{Lo: pts[0].Y, Hi: pts[0].Y},
}
for _, p := range pts[1:] {
r = r.AddPoint(p)
}
return r
}
// RectFromCenterSize constructs a rectangle with the given center and size.
// Both dimensions of size must be non-negative.
func RectFromCenterSize(center, size Point) Rect {
return Rect{
r1.Interval{Lo: center.X - size.X/2, Hi: center.X + size.X/2},
r1.Interval{Lo: center.Y - size.Y/2, Hi: center.Y + size.Y/2},
}
}
// EmptyRect constructs the canonical empty rectangle. Use IsEmpty() to test
// for empty rectangles, since they have more than one representation. A Rect{}
// is not the same as the EmptyRect.
func EmptyRect() Rect {
return Rect{r1.EmptyInterval(), r1.EmptyInterval()}
}
// IsValid reports whether the rectangle is valid.
// This requires the width to be empty iff the height is empty.
func (r Rect) IsValid() bool {
return r.X.IsEmpty() == r.Y.IsEmpty()
}
// IsEmpty reports whether the rectangle is empty.
func (r Rect) IsEmpty() bool {
return r.X.IsEmpty()
}
// Vertices returns all four vertices of the rectangle. Vertices are returned in
// CCW direction starting with the lower left corner.
func (r Rect) Vertices() [4]Point {
return [4]Point{
{r.X.Lo, r.Y.Lo},
{r.X.Hi, r.Y.Lo},
{r.X.Hi, r.Y.Hi},
{r.X.Lo, r.Y.Hi},
}
}
// VertexIJ returns the vertex in direction i along the X-axis (0=left, 1=right) and
// direction j along the Y-axis (0=down, 1=up).
func (r Rect) VertexIJ(i, j int) Point {
x := r.X.Lo
if i == 1 {
x = r.X.Hi
}
y := r.Y.Lo
if j == 1 {
y = r.Y.Hi
}
return Point{x, y}
}
// Lo returns the low corner of the rect.
func (r Rect) Lo() Point {
return Point{r.X.Lo, r.Y.Lo}
}
// Hi returns the high corner of the rect.
func (r Rect) Hi() Point {
return Point{r.X.Hi, r.Y.Hi}
}
// Center returns the center of the rectangle in (x,y)-space
func (r Rect) Center() Point {
return Point{r.X.Center(), r.Y.Center()}
}
// Size returns the width and height of this rectangle in (x,y)-space. Empty
// rectangles have a negative width and height.
func (r Rect) Size() Point {
return Point{r.X.Length(), r.Y.Length()}
}
// ContainsPoint reports whether the rectangle contains the given point.
// Rectangles are closed regions, i.e. they contain their boundary.
func (r Rect) ContainsPoint(p Point) bool {
return r.X.Contains(p.X) && r.Y.Contains(p.Y)
}
// InteriorContainsPoint returns true iff the given point is contained in the interior
// of the region (i.e. the region excluding its boundary).
func (r Rect) InteriorContainsPoint(p Point) bool {
return r.X.InteriorContains(p.X) && r.Y.InteriorContains(p.Y)
}
// Contains reports whether the rectangle contains the given rectangle.
func (r Rect) Contains(other Rect) bool {
return r.X.ContainsInterval(other.X) && r.Y.ContainsInterval(other.Y)
}
// InteriorContains reports whether the interior of this rectangle contains all of the
// points of the given other rectangle (including its boundary).
func (r Rect) InteriorContains(other Rect) bool {
return r.X.InteriorContainsInterval(other.X) && r.Y.InteriorContainsInterval(other.Y)
}
// Intersects reports whether this rectangle and the other rectangle have any points in common.
func (r Rect) Intersects(other Rect) bool {
return r.X.Intersects(other.X) && r.Y.Intersects(other.Y)
}
// InteriorIntersects reports whether the interior of this rectangle intersects
// any point (including the boundary) of the given other rectangle.
func (r Rect) InteriorIntersects(other Rect) bool {
return r.X.InteriorIntersects(other.X) && r.Y.InteriorIntersects(other.Y)
}
// AddPoint expands the rectangle to include the given point. The rectangle is
// expanded by the minimum amount possible.
func (r Rect) AddPoint(p Point) Rect {
return Rect{r.X.AddPoint(p.X), r.Y.AddPoint(p.Y)}
}
// AddRect expands the rectangle to include the given rectangle. This is the
// same as replacing the rectangle by the union of the two rectangles, but
// is more efficient.
func (r Rect) AddRect(other Rect) Rect {
return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}
// ClampPoint returns the closest point in the rectangle to the given point.
// The rectangle must be non-empty.
func (r Rect) ClampPoint(p Point) Point {
return Point{r.X.ClampPoint(p.X), r.Y.ClampPoint(p.Y)}
}
// Expanded returns a rectangle that has been expanded in the x-direction
// by margin.X, and in y-direction by margin.Y. If either margin is empty,
// then shrink the interval on the corresponding sides instead. The resulting
// rectangle may be empty. Any expansion of an empty rectangle remains empty.
func (r Rect) Expanded(margin Point) Rect {
xx := r.X.Expanded(margin.X)
yy := r.Y.Expanded(margin.Y)
if xx.IsEmpty() || yy.IsEmpty() {
return EmptyRect()
}
return Rect{xx, yy}
}
// ExpandedByMargin returns a Rect that has been expanded by the amount on all sides.
func (r Rect) ExpandedByMargin(margin float64) Rect {
return r.Expanded(Point{margin, margin})
}
// Union returns the smallest rectangle containing the union of this rectangle and
// the given rectangle.
func (r Rect) Union(other Rect) Rect {
return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}
// Intersection returns the smallest rectangle containing the intersection of this
// rectangle and the given rectangle.
func (r Rect) Intersection(other Rect) Rect {
xx := r.X.Intersection(other.X)
yy := r.Y.Intersection(other.Y)
if xx.IsEmpty() || yy.IsEmpty() {
return EmptyRect()
}
return Rect{xx, yy}
}
// ApproxEqual returns true if the x- and y-intervals of the two rectangles are
// the same up to the given tolerance.
func (r Rect) ApproxEqual(r2 Rect) bool {
return r.X.ApproxEqual(r2.X) && r.Y.ApproxEqual(r2.Y)
}
func (r Rect) String() string { return fmt.Sprintf("[Lo%s, Hi%s]", r.Lo(), r.Hi()) }

View file

@ -1,20 +0,0 @@
// Copyright 2014 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 r3 implements types and functions for working with geometry in ³.
See ../s2 for a more detailed overview.
*/
package r3

View file

@ -1,198 +0,0 @@
// Copyright 2016 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 r3
import (
"fmt"
"math/big"
)
const (
// prec is the number of bits of precision to use for the Float values.
// To keep things simple, we use the maximum allowable precision on big
// values. This allows us to handle all values we expect in the s2 library.
prec = big.MaxPrec
)
// define some commonly referenced values.
var (
precise0 = precInt(0)
precise1 = precInt(1)
)
// precStr wraps the conversion from a string into a big.Float. For results that
// actually can be represented exactly, this should only be used on values that
// are integer multiples of integer powers of 2.
func precStr(s string) *big.Float {
// Explicitly ignoring the bool return for this usage.
f, _ := new(big.Float).SetPrec(prec).SetString(s)
return f
}
func precInt(i int64) *big.Float {
return new(big.Float).SetPrec(prec).SetInt64(i)
}
func precFloat(f float64) *big.Float {
return new(big.Float).SetPrec(prec).SetFloat64(f)
}
func precAdd(a, b *big.Float) *big.Float {
return new(big.Float).SetPrec(prec).Add(a, b)
}
func precSub(a, b *big.Float) *big.Float {
return new(big.Float).SetPrec(prec).Sub(a, b)
}
func precMul(a, b *big.Float) *big.Float {
return new(big.Float).SetPrec(prec).Mul(a, b)
}
// PreciseVector represents a point in ℝ³ using high-precision values.
// Note that this is NOT a complete implementation because there are some
// operations that Vector supports that are not feasible with arbitrary precision
// math. (e.g., methods that need divison like Normalize, or methods needing a
// square root operation such as Norm)
type PreciseVector struct {
X, Y, Z *big.Float
}
// PreciseVectorFromVector creates a high precision vector from the given Vector.
func PreciseVectorFromVector(v Vector) PreciseVector {
return NewPreciseVector(v.X, v.Y, v.Z)
}
// NewPreciseVector creates a high precision vector from the given floating point values.
func NewPreciseVector(x, y, z float64) PreciseVector {
return PreciseVector{
X: precFloat(x),
Y: precFloat(y),
Z: precFloat(z),
}
}
// Vector returns this precise vector converted to a Vector.
func (v PreciseVector) Vector() Vector {
// The accuracy flag is ignored on these conversions back to float64.
x, _ := v.X.Float64()
y, _ := v.Y.Float64()
z, _ := v.Z.Float64()
return Vector{x, y, z}.Normalize()
}
// Equal reports whether v and ov are equal.
func (v PreciseVector) Equal(ov PreciseVector) bool {
return v.X.Cmp(ov.X) == 0 && v.Y.Cmp(ov.Y) == 0 && v.Z.Cmp(ov.Z) == 0
}
func (v PreciseVector) String() string {
return fmt.Sprintf("(%10g, %10g, %10g)", v.X, v.Y, v.Z)
}
// Norm2 returns the square of the norm.
func (v PreciseVector) Norm2() *big.Float { return v.Dot(v) }
// IsUnit reports whether this vector is of unit length.
func (v PreciseVector) IsUnit() bool {
return v.Norm2().Cmp(precise1) == 0
}
// Abs returns the vector with nonnegative components.
func (v PreciseVector) Abs() PreciseVector {
return PreciseVector{
X: new(big.Float).Abs(v.X),
Y: new(big.Float).Abs(v.Y),
Z: new(big.Float).Abs(v.Z),
}
}
// Add returns the standard vector sum of v and ov.
func (v PreciseVector) Add(ov PreciseVector) PreciseVector {
return PreciseVector{
X: precAdd(v.X, ov.X),
Y: precAdd(v.Y, ov.Y),
Z: precAdd(v.Z, ov.Z),
}
}
// Sub returns the standard vector difference of v and ov.
func (v PreciseVector) Sub(ov PreciseVector) PreciseVector {
return PreciseVector{
X: precSub(v.X, ov.X),
Y: precSub(v.Y, ov.Y),
Z: precSub(v.Z, ov.Z),
}
}
// Mul returns the standard scalar product of v and f.
func (v PreciseVector) Mul(f *big.Float) PreciseVector {
return PreciseVector{
X: precMul(v.X, f),
Y: precMul(v.Y, f),
Z: precMul(v.Z, f),
}
}
// MulByFloat64 returns the standard scalar product of v and f.
func (v PreciseVector) MulByFloat64(f float64) PreciseVector {
return v.Mul(precFloat(f))
}
// Dot returns the standard dot product of v and ov.
func (v PreciseVector) Dot(ov PreciseVector) *big.Float {
return precAdd(precMul(v.X, ov.X), precAdd(precMul(v.Y, ov.Y), precMul(v.Z, ov.Z)))
}
// Cross returns the standard cross product of v and ov.
func (v PreciseVector) Cross(ov PreciseVector) PreciseVector {
return PreciseVector{
X: precSub(precMul(v.Y, ov.Z), precMul(v.Z, ov.Y)),
Y: precSub(precMul(v.Z, ov.X), precMul(v.X, ov.Z)),
Z: precSub(precMul(v.X, ov.Y), precMul(v.Y, ov.X)),
}
}
// LargestComponent returns the axis that represents the largest component in this vector.
func (v PreciseVector) LargestComponent() Axis {
t := v.Abs()
if t.X.Cmp(t.Y) > 0 {
if t.X.Cmp(t.Z) > 0 {
return XAxis
}
return ZAxis
}
if t.Y.Cmp(t.Z) > 0 {
return YAxis
}
return ZAxis
}
// SmallestComponent returns the axis that represents the smallest component in this vector.
func (v PreciseVector) SmallestComponent() Axis {
t := v.Abs()
if t.X.Cmp(t.Y) < 0 {
if t.X.Cmp(t.Z) < 0 {
return XAxis
}
return ZAxis
}
if t.Y.Cmp(t.Z) < 0 {
return YAxis
}
return ZAxis
}

View file

@ -1,183 +0,0 @@
// Copyright 2014 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 r3
import (
"fmt"
"math"
"github.com/golang/geo/s1"
)
// Vector represents a point in ℝ³.
type Vector struct {
X, Y, Z float64
}
// ApproxEqual reports whether v and ov are equal within a small epsilon.
func (v Vector) ApproxEqual(ov Vector) bool {
const epsilon = 1e-16
return math.Abs(v.X-ov.X) < epsilon && math.Abs(v.Y-ov.Y) < epsilon && math.Abs(v.Z-ov.Z) < epsilon
}
func (v Vector) String() string { return fmt.Sprintf("(%0.24f, %0.24f, %0.24f)", v.X, v.Y, v.Z) }
// Norm returns the vector's norm.
func (v Vector) Norm() float64 { return math.Sqrt(v.Dot(v)) }
// Norm2 returns the square of the norm.
func (v Vector) Norm2() float64 { return v.Dot(v) }
// Normalize returns a unit vector in the same direction as v.
func (v Vector) Normalize() Vector {
n2 := v.Norm2()
if n2 == 0 {
return Vector{0, 0, 0}
}
return v.Mul(1 / math.Sqrt(n2))
}
// IsUnit returns whether this vector is of approximately unit length.
func (v Vector) IsUnit() bool {
const epsilon = 5e-14
return math.Abs(v.Norm2()-1) <= epsilon
}
// Abs returns the vector with nonnegative components.
func (v Vector) Abs() Vector { return Vector{math.Abs(v.X), math.Abs(v.Y), math.Abs(v.Z)} }
// Add returns the standard vector sum of v and ov.
func (v Vector) Add(ov Vector) Vector { return Vector{v.X + ov.X, v.Y + ov.Y, v.Z + ov.Z} }
// Sub returns the standard vector difference of v and ov.
func (v Vector) Sub(ov Vector) Vector { return Vector{v.X - ov.X, v.Y - ov.Y, v.Z - ov.Z} }
// Mul returns the standard scalar product of v and m.
func (v Vector) Mul(m float64) Vector { return Vector{m * v.X, m * v.Y, m * v.Z} }
// Dot returns the standard dot product of v and ov.
func (v Vector) Dot(ov Vector) float64 { return v.X*ov.X + v.Y*ov.Y + v.Z*ov.Z }
// Cross returns the standard cross product of v and ov.
func (v Vector) Cross(ov Vector) Vector {
return Vector{
v.Y*ov.Z - v.Z*ov.Y,
v.Z*ov.X - v.X*ov.Z,
v.X*ov.Y - v.Y*ov.X,
}
}
// Distance returns the Euclidean distance between v and ov.
func (v Vector) Distance(ov Vector) float64 { return v.Sub(ov).Norm() }
// Angle returns the angle between v and ov.
func (v Vector) Angle(ov Vector) s1.Angle {
return s1.Angle(math.Atan2(v.Cross(ov).Norm(), v.Dot(ov))) * s1.Radian
}
// Axis enumerates the 3 axes of ℝ³.
type Axis int
// The three axes of ℝ³.
const (
XAxis Axis = iota
YAxis
ZAxis
)
// Ortho returns a unit vector that is orthogonal to v.
// Ortho(-v) = -Ortho(v) for all v.
func (v Vector) Ortho() Vector {
ov := Vector{0.012, 0.0053, 0.00457}
switch v.LargestComponent() {
case XAxis:
ov.Z = 1
case YAxis:
ov.X = 1
default:
ov.Y = 1
}
return v.Cross(ov).Normalize()
}
// LargestComponent returns the axis that represents the largest component in this vector.
func (v Vector) LargestComponent() Axis {
t := v.Abs()
if t.X > t.Y {
if t.X > t.Z {
return XAxis
}
return ZAxis
}
if t.Y > t.Z {
return YAxis
}
return ZAxis
}
// SmallestComponent returns the axis that represents the smallest component in this vector.
func (v Vector) SmallestComponent() Axis {
t := v.Abs()
if t.X < t.Y {
if t.X < t.Z {
return XAxis
}
return ZAxis
}
if t.Y < t.Z {
return YAxis
}
return ZAxis
}
// Cmp compares v and ov lexicographically and returns:
//
// -1 if v < ov
// 0 if v == ov
// +1 if v > ov
//
// This method is based on C++'s std::lexicographical_compare. Two entities
// are compared element by element with the given operator. The first mismatch
// defines which is less (or greater) than the other. If both have equivalent
// values they are lexicographically equal.
func (v Vector) Cmp(ov Vector) int {
if v.X < ov.X {
return -1
}
if v.X > ov.X {
return 1
}
// First elements were the same, try the next.
if v.Y < ov.Y {
return -1
}
if v.Y > ov.Y {
return 1
}
// Second elements were the same return the final compare.
if v.Z < ov.Z {
return -1
}
if v.Z > ov.Z {
return 1
}
// Both are equal
return 0
}

View file

@ -1,117 +0,0 @@
// Copyright 2014 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 s1
import (
"math"
"strconv"
)
// Angle represents a 1D angle. The internal representation is a double precision
// value in radians, so conversion to and from radians is exact.
// Conversions between E5, E6, E7, and Degrees are not always
// exact. For example, Degrees(3.1) is different from E6(3100000) or E7(310000000).
//
// The following conversions between degrees and radians are exact:
//
// Degree*180 == Radian*math.Pi
// Degree*(180/n) == Radian*(math.Pi/n) for n == 0..8
//
// These identities hold when the arguments are scaled up or down by any power
// of 2. Some similar identities are also true, for example,
//
// Degree*60 == Radian*(math.Pi/3)
//
// But be aware that this type of identity does not hold in general. For example,
//
// Degree*3 != Radian*(math.Pi/60)
//
// Similarly, the conversion to radians means that (Angle(x)*Degree).Degrees()
// does not always equal x. For example,
//
// (Angle(45*n)*Degree).Degrees() == 45*n for n == 0..8
//
// but
//
// (60*Degree).Degrees() != 60
//
// When testing for equality, you should allow for numerical errors (floatApproxEq)
// or convert to discrete E5/E6/E7 values first.
type Angle float64
// Angle units.
const (
Radian Angle = 1
Degree = (math.Pi / 180) * Radian
E5 = 1e-5 * Degree
E6 = 1e-6 * Degree
E7 = 1e-7 * Degree
)
// Radians returns the angle in radians.
func (a Angle) Radians() float64 { return float64(a) }
// Degrees returns the angle in degrees.
func (a Angle) Degrees() float64 { return float64(a / Degree) }
// round returns the value rounded to nearest as an int32.
// This does not match C++ exactly for the case of x.5.
func round(val float64) int32 {
if val < 0 {
return int32(val - 0.5)
}
return int32(val + 0.5)
}
// InfAngle returns an angle larger than any finite angle.
func InfAngle() Angle {
return Angle(math.Inf(1))
}
// isInf reports whether this Angle is infinite.
func (a Angle) isInf() bool {
return math.IsInf(float64(a), 0)
}
// E5 returns the angle in hundred thousandths of degrees.
func (a Angle) E5() int32 { return round(a.Degrees() * 1e5) }
// E6 returns the angle in millionths of degrees.
func (a Angle) E6() int32 { return round(a.Degrees() * 1e6) }
// E7 returns the angle in ten millionths of degrees.
func (a Angle) E7() int32 { return round(a.Degrees() * 1e7) }
// Abs returns the absolute value of the angle.
func (a Angle) Abs() Angle { return Angle(math.Abs(float64(a))) }
// Normalized returns an equivalent angle in [0, 2π).
func (a Angle) Normalized() Angle {
rad := math.Mod(float64(a), 2*math.Pi)
if rad < 0 {
rad += 2 * math.Pi
}
return Angle(rad)
}
func (a Angle) String() string {
return strconv.FormatFloat(a.Degrees(), 'f', 7, 64) // like "%.7f"
}
// BUG(dsymonds): The major differences from the C++ version are:
// - no unsigned E5/E6/E7 methods
// - no S2Point or S2LatLng constructors
// - no comparison or arithmetic operators

View file

@ -1,245 +0,0 @@
// Copyright 2015 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 s1
import (
"math"
)
// ChordAngle represents the angle subtended by a chord (i.e., the straight
// line segment connecting two points on the sphere). Its representation
// makes it very efficient for computing and comparing distances, but unlike
// Angle it is only capable of representing angles between 0 and π radians.
// Generally, ChordAngle should only be used in loops where many angles need
// to be calculated and compared. Otherwise it is simpler to use Angle.
//
// ChordAngle loses some accuracy as the angle approaches π radians.
// Specifically, the representation of (π - x) radians has an error of about
// (1e-15 / x), with a maximum error of about 2e-8 radians (about 13cm on the
// Earth's surface). For comparison, for angles up to π/2 radians (10000km)
// the worst-case representation error is about 2e-16 radians (1 nanonmeter),
// which is about the same as Angle.
//
// ChordAngles are represented by the squared chord length, which can
// range from 0 to 4. Positive infinity represents an infinite squared length.
type ChordAngle float64
const (
// NegativeChordAngle represents a chord angle smaller than the zero angle.
// The only valid operations on a NegativeChordAngle are comparisons,
// Angle conversions, and Successor/Predecessor.
NegativeChordAngle = ChordAngle(-1)
// RightChordAngle represents a chord angle of 90 degrees (a "right angle").
RightChordAngle = ChordAngle(2)
// StraightChordAngle represents a chord angle of 180 degrees (a "straight angle").
// This is the maximum finite chord angle.
StraightChordAngle = ChordAngle(4)
// maxLength2 is the square of the maximum length allowed in a ChordAngle.
maxLength2 = 4.0
)
// ChordAngleFromAngle returns a ChordAngle from the given Angle.
func ChordAngleFromAngle(a Angle) ChordAngle {
if a < 0 {
return NegativeChordAngle
}
if a.isInf() {
return InfChordAngle()
}
l := 2 * math.Sin(0.5*math.Min(math.Pi, a.Radians()))
return ChordAngle(l * l)
}
// ChordAngleFromSquaredLength returns a ChordAngle from the squared chord length.
// Note that the argument is automatically clamped to a maximum of 4 to
// handle possible roundoff errors. The argument must be non-negative.
func ChordAngleFromSquaredLength(length2 float64) ChordAngle {
if length2 > maxLength2 {
return StraightChordAngle
}
return ChordAngle(length2)
}
// Expanded returns a new ChordAngle that has been adjusted by the given error
// bound (which can be positive or negative). Error should be the value
// returned by either MaxPointError or MaxAngleError. For example:
// a := ChordAngleFromPoints(x, y)
// a1 := a.Expanded(a.MaxPointError())
func (c ChordAngle) Expanded(e float64) ChordAngle {
// If the angle is special, don't change it. Otherwise clamp it to the valid range.
if c.isSpecial() {
return c
}
return ChordAngle(math.Max(0.0, math.Min(maxLength2, float64(c)+e)))
}
// Angle converts this ChordAngle to an Angle.
func (c ChordAngle) Angle() Angle {
if c < 0 {
return -1 * Radian
}
if c.isInf() {
return InfAngle()
}
return Angle(2 * math.Asin(0.5*math.Sqrt(float64(c))))
}
// InfChordAngle returns a chord angle larger than any finite chord angle.
// The only valid operations on an InfChordAngle are comparisons, Angle
// conversions, and Successor/Predecessor.
func InfChordAngle() ChordAngle {
return ChordAngle(math.Inf(1))
}
// isInf reports whether this ChordAngle is infinite.
func (c ChordAngle) isInf() bool {
return math.IsInf(float64(c), 1)
}
// isSpecial reports whether this ChordAngle is one of the special cases.
func (c ChordAngle) isSpecial() bool {
return c < 0 || c.isInf()
}
// isValid reports whether this ChordAngle is valid or not.
func (c ChordAngle) isValid() bool {
return (c >= 0 && c <= maxLength2) || c.isSpecial()
}
// Successor returns the smallest representable ChordAngle larger than this one.
// This can be used to convert a "<" comparison to a "<=" comparison.
//
// Note the following special cases:
// NegativeChordAngle.Successor == 0
// StraightChordAngle.Successor == InfChordAngle
// InfChordAngle.Successor == InfChordAngle
func (c ChordAngle) Successor() ChordAngle {
if c >= maxLength2 {
return InfChordAngle()
}
if c < 0 {
return 0
}
return ChordAngle(math.Nextafter(float64(c), 10.0))
}
// Predecessor returns the largest representable ChordAngle less than this one.
//
// Note the following special cases:
// InfChordAngle.Predecessor == StraightChordAngle
// ChordAngle(0).Predecessor == NegativeChordAngle
// NegativeChordAngle.Predecessor == NegativeChordAngle
func (c ChordAngle) Predecessor() ChordAngle {
if c <= 0 {
return NegativeChordAngle
}
if c > maxLength2 {
return StraightChordAngle
}
return ChordAngle(math.Nextafter(float64(c), -10.0))
}
// MaxPointError returns the maximum error size for a ChordAngle constructed
// from 2 Points x and y, assuming that x and y are normalized to within the
// bounds guaranteed by s2.Point.Normalize. The error is defined with respect to
// the true distance after the points are projected to lie exactly on the sphere.
func (c ChordAngle) MaxPointError() float64 {
// There is a relative error of (2.5*dblEpsilon) when computing the squared
// distance, plus a relative error of 2 * dblEpsilon, plus an absolute error
// of (16 * dblEpsilon**2) because the lengths of the input points may differ
// from 1 by up to (2*dblEpsilon) each. (This is the maximum error in Normalize).
return 4.5*dblEpsilon*float64(c) + 16*dblEpsilon*dblEpsilon
}
// MaxAngleError returns the maximum error for a ChordAngle constructed
// as an Angle distance.
func (c ChordAngle) MaxAngleError() float64 {
return dblEpsilon * float64(c)
}
// Add adds the other ChordAngle to this one and returns the resulting value.
// This method assumes the ChordAngles are not special.
func (c ChordAngle) Add(other ChordAngle) ChordAngle {
// Note that this method (and Sub) is much more efficient than converting
// the ChordAngle to an Angle and adding those and converting back. It
// requires only one square root plus a few additions and multiplications.
// Optimization for the common case where b is an error tolerance
// parameter that happens to be set to zero.
if other == 0 {
return c
}
// Clamp the angle sum to at most 180 degrees.
if c+other >= maxLength2 {
return StraightChordAngle
}
// Let a and b be the (non-squared) chord lengths, and let c = a+b.
// Let A, B, and C be the corresponding half-angles (a = 2*sin(A), etc).
// Then the formula below can be derived from c = 2 * sin(A+B) and the
// relationships sin(A+B) = sin(A)*cos(B) + sin(B)*cos(A)
// cos(X) = sqrt(1 - sin^2(X))
x := float64(c * (1 - 0.25*other))
y := float64(other * (1 - 0.25*c))
return ChordAngle(math.Min(maxLength2, x+y+2*math.Sqrt(x*y)))
}
// Sub subtracts the other ChordAngle from this one and returns the resulting
// value. This method assumes the ChordAngles are not special.
func (c ChordAngle) Sub(other ChordAngle) ChordAngle {
if other == 0 {
return c
}
if c <= other {
return 0
}
x := float64(c * (1 - 0.25*other))
y := float64(other * (1 - 0.25*c))
return ChordAngle(math.Max(0.0, x+y-2*math.Sqrt(x*y)))
}
// Sin returns the sine of this chord angle. This method is more efficient
// than converting to Angle and performing the computation.
func (c ChordAngle) Sin() float64 {
return math.Sqrt(c.Sin2())
}
// Sin2 returns the square of the sine of this chord angle.
// It is more efficient than Sin.
func (c ChordAngle) Sin2() float64 {
// Let a be the (non-squared) chord length, and let A be the corresponding
// half-angle (a = 2*sin(A)). The formula below can be derived from:
// sin(2*A) = 2 * sin(A) * cos(A)
// cos^2(A) = 1 - sin^2(A)
// This is much faster than converting to an angle and computing its sine.
return float64(c * (1 - 0.25*c))
}
// Cos returns the cosine of this chord angle. This method is more efficient
// than converting to Angle and performing the computation.
func (c ChordAngle) Cos() float64 {
// cos(2*A) = cos^2(A) - sin^2(A) = 1 - 2*sin^2(A)
return float64(1 - 0.5*c)
}
// Tan returns the tangent of this chord angle.
func (c ChordAngle) Tan() float64 {
return c.Sin() / c.Cos()
}

View file

@ -1,20 +0,0 @@
// Copyright 2014 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 s1 implements types and functions for working with geometry in S¹ (circular geometry).
See ../s2 for a more detailed overview.
*/
package s1

View file

@ -1,348 +0,0 @@
// Copyright 2014 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 s1
import (
"math"
"strconv"
)
// Interval represents a closed interval on a unit circle.
// Zero-length intervals (where Lo == Hi) represent single points.
// If Lo > Hi then the interval is "inverted".
// The point at (-1, 0) on the unit circle has two valid representations,
// [π,π] and [-π,-π]. We normalize the latter to the former in IntervalFromEndpoints.
// There are two special intervals that take advantage of that:
// - the full interval, [-π,π], and
// - the empty interval, [π,-π].
// Treat the exported fields as read-only.
type Interval struct {
Lo, Hi float64
}
// IntervalFromEndpoints constructs a new interval from endpoints.
// Both arguments must be in the range [-π,π]. This function allows inverted intervals
// to be created.
func IntervalFromEndpoints(lo, hi float64) Interval {
i := Interval{lo, hi}
if lo == -math.Pi && hi != math.Pi {
i.Lo = math.Pi
}
if hi == -math.Pi && lo != math.Pi {
i.Hi = math.Pi
}
return i
}
// IntervalFromPointPair returns the minimal interval containing the two given points.
// Both arguments must be in [-π,π].
func IntervalFromPointPair(a, b float64) Interval {
if a == -math.Pi {
a = math.Pi
}
if b == -math.Pi {
b = math.Pi
}
if positiveDistance(a, b) <= math.Pi {
return Interval{a, b}
}
return Interval{b, a}
}
// EmptyInterval returns an empty interval.
func EmptyInterval() Interval { return Interval{math.Pi, -math.Pi} }
// FullInterval returns a full interval.
func FullInterval() Interval { return Interval{-math.Pi, math.Pi} }
// IsValid reports whether the interval is valid.
func (i Interval) IsValid() bool {
return (math.Abs(i.Lo) <= math.Pi && math.Abs(i.Hi) <= math.Pi &&
!(i.Lo == -math.Pi && i.Hi != math.Pi) &&
!(i.Hi == -math.Pi && i.Lo != math.Pi))
}
// IsFull reports whether the interval is full.
func (i Interval) IsFull() bool { return i.Lo == -math.Pi && i.Hi == math.Pi }
// IsEmpty reports whether the interval is empty.
func (i Interval) IsEmpty() bool { return i.Lo == math.Pi && i.Hi == -math.Pi }
// IsInverted reports whether the interval is inverted; that is, whether Lo > Hi.
func (i Interval) IsInverted() bool { return i.Lo > i.Hi }
// Invert returns the interval with endpoints swapped.
func (i Interval) Invert() Interval {
return Interval{i.Hi, i.Lo}
}
// Center returns the midpoint of the interval.
// It is undefined for full and empty intervals.
func (i Interval) Center() float64 {
c := 0.5 * (i.Lo + i.Hi)
if !i.IsInverted() {
return c
}
if c <= 0 {
return c + math.Pi
}
return c - math.Pi
}
// Length returns the length of the interval.
// The length of an empty interval is negative.
func (i Interval) Length() float64 {
l := i.Hi - i.Lo
if l >= 0 {
return l
}
l += 2 * math.Pi
if l > 0 {
return l
}
return -1
}
// Assumes p ∈ (-π,π].
func (i Interval) fastContains(p float64) bool {
if i.IsInverted() {
return (p >= i.Lo || p <= i.Hi) && !i.IsEmpty()
}
return p >= i.Lo && p <= i.Hi
}
// Contains returns true iff the interval contains p.
// Assumes p ∈ [-π,π].
func (i Interval) Contains(p float64) bool {
if p == -math.Pi {
p = math.Pi
}
return i.fastContains(p)
}
// ContainsInterval returns true iff the interval contains oi.
func (i Interval) ContainsInterval(oi Interval) bool {
if i.IsInverted() {
if oi.IsInverted() {
return oi.Lo >= i.Lo && oi.Hi <= i.Hi
}
return (oi.Lo >= i.Lo || oi.Hi <= i.Hi) && !i.IsEmpty()
}
if oi.IsInverted() {
return i.IsFull() || oi.IsEmpty()
}
return oi.Lo >= i.Lo && oi.Hi <= i.Hi
}
// InteriorContains returns true iff the interior of the interval contains p.
// Assumes p ∈ [-π,π].
func (i Interval) InteriorContains(p float64) bool {
if p == -math.Pi {
p = math.Pi
}
if i.IsInverted() {
return p > i.Lo || p < i.Hi
}
return (p > i.Lo && p < i.Hi) || i.IsFull()
}
// InteriorContainsInterval returns true iff the interior of the interval contains oi.
func (i Interval) InteriorContainsInterval(oi Interval) bool {
if i.IsInverted() {
if oi.IsInverted() {
return (oi.Lo > i.Lo && oi.Hi < i.Hi) || oi.IsEmpty()
}
return oi.Lo > i.Lo || oi.Hi < i.Hi
}
if oi.IsInverted() {
return i.IsFull() || oi.IsEmpty()
}
return (oi.Lo > i.Lo && oi.Hi < i.Hi) || i.IsFull()
}
// Intersects returns true iff the interval contains any points in common with oi.
func (i Interval) Intersects(oi Interval) bool {
if i.IsEmpty() || oi.IsEmpty() {
return false
}
if i.IsInverted() {
return oi.IsInverted() || oi.Lo <= i.Hi || oi.Hi >= i.Lo
}
if oi.IsInverted() {
return oi.Lo <= i.Hi || oi.Hi >= i.Lo
}
return oi.Lo <= i.Hi && oi.Hi >= i.Lo
}
// InteriorIntersects returns true iff the interior of the interval contains any points in common with oi, including the latter's boundary.
func (i Interval) InteriorIntersects(oi Interval) bool {
if i.IsEmpty() || oi.IsEmpty() || i.Lo == i.Hi {
return false
}
if i.IsInverted() {
return oi.IsInverted() || oi.Lo < i.Hi || oi.Hi > i.Lo
}
if oi.IsInverted() {
return oi.Lo < i.Hi || oi.Hi > i.Lo
}
return (oi.Lo < i.Hi && oi.Hi > i.Lo) || i.IsFull()
}
// Compute distance from a to b in [0,2π], in a numerically stable way.
func positiveDistance(a, b float64) float64 {
d := b - a
if d >= 0 {
return d
}
return (b + math.Pi) - (a - math.Pi)
}
// Union returns the smallest interval that contains both the interval and oi.
func (i Interval) Union(oi Interval) Interval {
if oi.IsEmpty() {
return i
}
if i.fastContains(oi.Lo) {
if i.fastContains(oi.Hi) {
// Either oi ⊂ i, or i oi is the full interval.
if i.ContainsInterval(oi) {
return i
}
return FullInterval()
}
return Interval{i.Lo, oi.Hi}
}
if i.fastContains(oi.Hi) {
return Interval{oi.Lo, i.Hi}
}
// Neither endpoint of oi is in i. Either i ⊂ oi, or i and oi are disjoint.
if i.IsEmpty() || oi.fastContains(i.Lo) {
return oi
}
// This is the only hard case where we need to find the closest pair of endpoints.
if positiveDistance(oi.Hi, i.Lo) < positiveDistance(i.Hi, oi.Lo) {
return Interval{oi.Lo, i.Hi}
}
return Interval{i.Lo, oi.Hi}
}
// Intersection returns the smallest interval that contains the intersection of the interval and oi.
func (i Interval) Intersection(oi Interval) Interval {
if oi.IsEmpty() {
return EmptyInterval()
}
if i.fastContains(oi.Lo) {
if i.fastContains(oi.Hi) {
// Either oi ⊂ i, or i and oi intersect twice. Neither are empty.
// In the first case we want to return i (which is shorter than oi).
// In the second case one of them is inverted, and the smallest interval
// that covers the two disjoint pieces is the shorter of i and oi.
// We thus want to pick the shorter of i and oi in both cases.
if oi.Length() < i.Length() {
return oi
}
return i
}
return Interval{oi.Lo, i.Hi}
}
if i.fastContains(oi.Hi) {
return Interval{i.Lo, oi.Hi}
}
// Neither endpoint of oi is in i. Either i ⊂ oi, or i and oi are disjoint.
if oi.fastContains(i.Lo) {
return i
}
return EmptyInterval()
}
// AddPoint returns the interval expanded by the minimum amount necessary such
// that it contains the given point "p" (an angle in the range [-Pi, Pi]).
func (i Interval) AddPoint(p float64) Interval {
if math.Abs(p) > math.Pi {
return i
}
if p == -math.Pi {
p = math.Pi
}
if i.fastContains(p) {
return i
}
if i.IsEmpty() {
return Interval{p, p}
}
if positiveDistance(p, i.Lo) < positiveDistance(i.Hi, p) {
return Interval{p, i.Hi}
}
return Interval{i.Lo, p}
}
// Define the maximum rounding error for arithmetic operations. Depending on the
// platform the mantissa precision may be different than others, so we choose to
// use specific values to be consistent across all.
// The values come from the C++ implementation.
var (
// epsilon is a small number that represents a reasonable level of noise between two
// values that can be considered to be equal.
epsilon = 1e-15
// dblEpsilon is a smaller number for values that require more precision.
dblEpsilon = 2.220446049e-16
)
// Expanded returns an interval that has been expanded on each side by margin.
// If margin is negative, then the function shrinks the interval on
// each side by margin instead. The resulting interval may be empty or
// full. Any expansion (positive or negative) of a full interval remains
// full, and any expansion of an empty interval remains empty.
func (i Interval) Expanded(margin float64) Interval {
if margin >= 0 {
if i.IsEmpty() {
return i
}
// Check whether this interval will be full after expansion, allowing
// for a rounding error when computing each endpoint.
if i.Length()+2*margin+2*dblEpsilon >= 2*math.Pi {
return FullInterval()
}
} else {
if i.IsFull() {
return i
}
// Check whether this interval will be empty after expansion, allowing
// for a rounding error when computing each endpoint.
if i.Length()+2*margin-2*dblEpsilon <= 0 {
return EmptyInterval()
}
}
result := IntervalFromEndpoints(
math.Remainder(i.Lo-margin, 2*math.Pi),
math.Remainder(i.Hi+margin, 2*math.Pi),
)
if result.Lo <= -math.Pi {
result.Lo = math.Pi
}
return result
}
func (i Interval) String() string {
// like "[%.7f, %.7f]"
return "[" + strconv.FormatFloat(i.Lo, 'f', 7, 64) + ", " + strconv.FormatFloat(i.Hi, 'f', 7, 64) + "]"
}
// BUG(dsymonds): The major differences from the C++ version are:
// - no validity checking on construction, etc. (not a bug?)
// - a few operations

View file

@ -1,53 +0,0 @@
// 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.
// +build !go1.9
package s2
// This file is for the bit manipulation code pre-Go 1.9.
// findMSBSetNonZero64 returns the index (between 0 and 63) of the most
// significant set bit. Passing zero to this function returns zero.
func findMSBSetNonZero64(x uint64) int {
val := []uint64{0x2, 0xC, 0xF0, 0xFF00, 0xFFFF0000, 0xFFFFFFFF00000000}
shift := []uint64{1, 2, 4, 8, 16, 32}
var msbPos uint64
for i := 5; i >= 0; i-- {
if x&val[i] != 0 {
x >>= shift[i]
msbPos |= shift[i]
}
}
return int(msbPos)
}
const deBruijn64 = 0x03f79d71b4ca8b09
const digitMask = uint64(1<<64 - 1)
var deBruijn64Lookup = []byte{
0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4,
62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5,
63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11,
54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
}
// findLSBSetNonZero64 returns the index (between 0 and 63) of the least
// significant set bit. Passing zero to this function returns zero.
//
// This code comes from trailingZeroBits in https://golang.org/src/math/big/nat.go
// which references (Knuth, volume 4, section 7.3.1).
func findLSBSetNonZero64(x uint64) int {
return int(deBruijn64Lookup[((x&-x)*(deBruijn64&digitMask))>>58])
}

View file

@ -1,39 +0,0 @@
// 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.
// +build go1.9
package s2
// This file is for the bit manipulation code post-Go 1.9.
import "math/bits"
// findMSBSetNonZero64 returns the index (between 0 and 63) of the most
// significant set bit. Passing zero to this function return zero.
func findMSBSetNonZero64(x uint64) int {
if x == 0 {
return 0
}
return 63 - bits.LeadingZeros64(x)
}
// findLSBSetNonZero64 returns the index (between 0 and 63) of the least
// significant set bit. Passing zero to this function return zero.
func findLSBSetNonZero64(x uint64) int {
if x == 0 {
return 0
}
return bits.TrailingZeros64(x)
}

View file

@ -1,519 +0,0 @@
// Copyright 2014 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 (
"fmt"
"io"
"math"
"github.com/golang/geo/r1"
"github.com/golang/geo/s1"
)
var (
// centerPoint is the default center for Caps
centerPoint = PointFromCoords(1.0, 0, 0)
)
// Cap represents a disc-shaped region defined by a center and radius.
// Technically this shape is called a "spherical cap" (rather than disc)
// because it is not planar; the cap represents a portion of the sphere that
// has been cut off by a plane. The boundary of the cap is the circle defined
// by the intersection of the sphere and the plane. For containment purposes,
// the cap is a closed set, i.e. it contains its boundary.
//
// For the most part, you can use a spherical cap wherever you would use a
// disc in planar geometry. The radius of the cap is measured along the
// surface of the sphere (rather than the straight-line distance through the
// interior). Thus a cap of radius π/2 is a hemisphere, and a cap of radius
// π covers the entire sphere.
//
// The center is a point on the surface of the unit sphere. (Hence the need for
// it to be of unit length.)
//
// A cap can also be defined by its center point and height. The height is the
// distance from the center point to the cutoff plane. There is also support for
// "empty" and "full" caps, which contain no points and all points respectively.
//
// Here are some useful relationships between the cap height (h), the cap
// radius (r), the maximum chord length from the cap's center (d), and the
// radius of cap's base (a).
//
// h = 1 - cos(r)
// = 2 * sin^2(r/2)
// d^2 = 2 * h
// = a^2 + h^2
//
// The zero value of Cap is an invalid cap. Use EmptyCap to get a valid empty cap.
type Cap struct {
center Point
radius s1.ChordAngle
}
// CapFromPoint constructs a cap containing a single point.
func CapFromPoint(p Point) Cap {
return CapFromCenterChordAngle(p, 0)
}
// CapFromCenterAngle constructs a cap with the given center and angle.
func CapFromCenterAngle(center Point, angle s1.Angle) Cap {
return CapFromCenterChordAngle(center, s1.ChordAngleFromAngle(angle))
}
// CapFromCenterChordAngle constructs a cap where the angle is expressed as an
// s1.ChordAngle. This constructor is more efficient than using an s1.Angle.
func CapFromCenterChordAngle(center Point, radius s1.ChordAngle) Cap {
return Cap{
center: center,
radius: radius,
}
}
// CapFromCenterHeight constructs a cap with the given center and height. A
// negative height yields an empty cap; a height of 2 or more yields a full cap.
// The center should be unit length.
func CapFromCenterHeight(center Point, height float64) Cap {
return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(2*height))
}
// CapFromCenterArea constructs a cap with the given center and surface area.
// Note that the area can also be interpreted as the solid angle subtended by the
// cap (because the sphere has unit radius). A negative area yields an empty cap;
// an area of 4*π or more yields a full cap.
func CapFromCenterArea(center Point, area float64) Cap {
return CapFromCenterChordAngle(center, s1.ChordAngleFromSquaredLength(area/math.Pi))
}
// EmptyCap returns a cap that contains no points.
func EmptyCap() Cap {
return CapFromCenterChordAngle(centerPoint, s1.NegativeChordAngle)
}
// FullCap returns a cap that contains all points.
func FullCap() Cap {
return CapFromCenterChordAngle(centerPoint, s1.StraightChordAngle)
}
// IsValid reports whether the Cap is considered valid.
func (c Cap) IsValid() bool {
return c.center.Vector.IsUnit() && c.radius <= s1.StraightChordAngle
}
// IsEmpty reports whether the cap is empty, i.e. it contains no points.
func (c Cap) IsEmpty() bool {
return c.radius < 0
}
// IsFull reports whether the cap is full, i.e. it contains all points.
func (c Cap) IsFull() bool {
return c.radius == s1.StraightChordAngle
}
// Center returns the cap's center point.
func (c Cap) Center() Point {
return c.center
}
// Height returns the height of the cap. This is the distance from the center
// point to the cutoff plane.
func (c Cap) Height() float64 {
return float64(0.5 * c.radius)
}
// Radius returns the cap radius as an s1.Angle. (Note that the cap angle
// is stored internally as a ChordAngle, so this method requires a trigonometric
// operation and may yield a slightly different result than the value passed
// to CapFromCenterAngle).
func (c Cap) Radius() s1.Angle {
return c.radius.Angle()
}
// Area returns the surface area of the Cap on the unit sphere.
func (c Cap) Area() float64 {
return 2.0 * math.Pi * math.Max(0, c.Height())
}
// Contains reports whether this cap contains the other.
func (c Cap) Contains(other Cap) bool {
// In a set containment sense, every cap contains the empty cap.
if c.IsFull() || other.IsEmpty() {
return true
}
return c.radius >= ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
}
// Intersects reports whether this cap intersects the other cap.
// i.e. whether they have any points in common.
func (c Cap) Intersects(other Cap) bool {
if c.IsEmpty() || other.IsEmpty() {
return false
}
return c.radius.Add(other.radius) >= ChordAngleBetweenPoints(c.center, other.center)
}
// InteriorIntersects reports whether this caps interior intersects the other cap.
func (c Cap) InteriorIntersects(other Cap) bool {
// Make sure this cap has an interior and the other cap is non-empty.
if c.radius <= 0 || other.IsEmpty() {
return false
}
return c.radius.Add(other.radius) > ChordAngleBetweenPoints(c.center, other.center)
}
// ContainsPoint reports whether this cap contains the point.
func (c Cap) ContainsPoint(p Point) bool {
return ChordAngleBetweenPoints(c.center, p) <= c.radius
}
// InteriorContainsPoint reports whether the point is within the interior of this cap.
func (c Cap) InteriorContainsPoint(p Point) bool {
return c.IsFull() || ChordAngleBetweenPoints(c.center, p) < c.radius
}
// Complement returns the complement of the interior of the cap. A cap and its
// complement have the same boundary but do not share any interior points.
// The complement operator is not a bijection because the complement of a
// singleton cap (containing a single point) is the same as the complement
// of an empty cap.
func (c Cap) Complement() Cap {
if c.IsFull() {
return EmptyCap()
}
if c.IsEmpty() {
return FullCap()
}
return CapFromCenterChordAngle(Point{c.center.Mul(-1)}, s1.StraightChordAngle.Sub(c.radius))
}
// CapBound returns a bounding spherical cap. This is not guaranteed to be exact.
func (c Cap) CapBound() Cap {
return c
}
// RectBound returns a bounding latitude-longitude rectangle.
// The bounds are not guaranteed to be tight.
func (c Cap) RectBound() Rect {
if c.IsEmpty() {
return EmptyRect()
}
capAngle := c.Radius().Radians()
allLongitudes := false
lat := r1.Interval{
Lo: latitude(c.center).Radians() - capAngle,
Hi: latitude(c.center).Radians() + capAngle,
}
lng := s1.FullInterval()
// Check whether cap includes the south pole.
if lat.Lo <= -math.Pi/2 {
lat.Lo = -math.Pi / 2
allLongitudes = true
}
// Check whether cap includes the north pole.
if lat.Hi >= math.Pi/2 {
lat.Hi = math.Pi / 2
allLongitudes = true
}
if !allLongitudes {
// Compute the range of longitudes covered by the cap. We use the law
// of sines for spherical triangles. Consider the triangle ABC where
// A is the north pole, B is the center of the cap, and C is the point
// of tangency between the cap boundary and a line of longitude. Then
// C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
// we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
// Here "a" is the cap angle, and "c" is the colatitude (90 degrees
// minus the latitude). This formula also works for negative latitudes.
//
// The formula for sin(a) follows from the relationship h = 1 - cos(a).
sinA := c.radius.Sin()
sinC := math.Cos(latitude(c.center).Radians())
if sinA <= sinC {
angleA := math.Asin(sinA / sinC)
lng.Lo = math.Remainder(longitude(c.center).Radians()-angleA, math.Pi*2)
lng.Hi = math.Remainder(longitude(c.center).Radians()+angleA, math.Pi*2)
}
}
return Rect{lat, lng}
}
// Equal reports whether this cap is equal to the other cap.
func (c Cap) Equal(other Cap) bool {
return (c.radius == other.radius && c.center == other.center) ||
(c.IsEmpty() && other.IsEmpty()) ||
(c.IsFull() && other.IsFull())
}
// ApproxEqual reports whether this cap is equal to the other cap within the given tolerance.
func (c Cap) ApproxEqual(other Cap) bool {
const epsilon = 1e-14
r2 := float64(c.radius)
otherR2 := float64(other.radius)
return c.center.ApproxEqual(other.center) &&
math.Abs(r2-otherR2) <= epsilon ||
c.IsEmpty() && otherR2 <= epsilon ||
other.IsEmpty() && r2 <= epsilon ||
c.IsFull() && otherR2 >= 2-epsilon ||
other.IsFull() && r2 >= 2-epsilon
}
// AddPoint increases the cap if necessary to include the given point. If this cap is empty,
// then the center is set to the point with a zero height. p must be unit-length.
func (c Cap) AddPoint(p Point) Cap {
if c.IsEmpty() {
c.center = p
c.radius = 0
return c
}
// After calling cap.AddPoint(p), cap.Contains(p) must be true. However
// we don't need to do anything special to achieve this because Contains()
// does exactly the same distance calculation that we do here.
if newRad := ChordAngleBetweenPoints(c.center, p); newRad > c.radius {
c.radius = newRad
}
return c
}
// AddCap increases the cap height if necessary to include the other cap. If this cap is empty,
// it is set to the other cap.
func (c Cap) AddCap(other Cap) Cap {
if c.IsEmpty() {
return other
}
if other.IsEmpty() {
return c
}
// We round up the distance to ensure that the cap is actually contained.
// TODO(roberts): Do some error analysis in order to guarantee this.
dist := ChordAngleBetweenPoints(c.center, other.center).Add(other.radius)
if newRad := dist.Expanded(dblEpsilon * float64(dist)); newRad > c.radius {
c.radius = newRad
}
return c
}
// Expanded returns a new cap expanded by the given angle. If the cap is empty,
// it returns an empty cap.
func (c Cap) Expanded(distance s1.Angle) Cap {
if c.IsEmpty() {
return EmptyCap()
}
return CapFromCenterChordAngle(c.center, c.radius.Add(s1.ChordAngleFromAngle(distance)))
}
func (c Cap) String() string {
return fmt.Sprintf("[Center=%v, Radius=%f]", c.center.Vector, c.Radius().Degrees())
}
// radiusToHeight converts an s1.Angle into the height of the cap.
func radiusToHeight(r s1.Angle) float64 {
if r.Radians() < 0 {
return float64(s1.NegativeChordAngle)
}
if r.Radians() >= math.Pi {
return float64(s1.RightChordAngle)
}
return float64(0.5 * s1.ChordAngleFromAngle(r))
}
// ContainsCell reports whether the cap contains the given cell.
func (c Cap) ContainsCell(cell Cell) bool {
// If the cap does not contain all cell vertices, return false.
var vertices [4]Point
for k := 0; k < 4; k++ {
vertices[k] = cell.Vertex(k)
if !c.ContainsPoint(vertices[k]) {
return false
}
}
// Otherwise, return true if the complement of the cap does not intersect the cell.
return !c.Complement().intersects(cell, vertices)
}
// IntersectsCell reports whether the cap intersects the cell.
func (c Cap) IntersectsCell(cell Cell) bool {
// If the cap contains any cell vertex, return true.
var vertices [4]Point
for k := 0; k < 4; k++ {
vertices[k] = cell.Vertex(k)
if c.ContainsPoint(vertices[k]) {
return true
}
}
return c.intersects(cell, vertices)
}
// intersects reports whether the cap intersects any point of the cell excluding
// its vertices (which are assumed to already have been checked).
func (c Cap) intersects(cell Cell, vertices [4]Point) bool {
// If the cap is a hemisphere or larger, the cell and the complement of the cap
// are both convex. Therefore since no vertex of the cell is contained, no other
// interior point of the cell is contained either.
if c.radius >= s1.RightChordAngle {
return false
}
// We need to check for empty caps due to the center check just below.
if c.IsEmpty() {
return false
}
// Optimization: return true if the cell contains the cap center. This allows half
// of the edge checks below to be skipped.
if cell.ContainsPoint(c.center) {
return true
}
// At this point we know that the cell does not contain the cap center, and the cap
// does not contain any cell vertex. The only way that they can intersect is if the
// cap intersects the interior of some edge.
sin2Angle := c.radius.Sin2()
for k := 0; k < 4; k++ {
edge := cell.Edge(k).Vector
dot := c.center.Vector.Dot(edge)
if dot > 0 {
// The center is in the interior half-space defined by the edge. We do not need
// to consider these edges, since if the cap intersects this edge then it also
// intersects the edge on the opposite side of the cell, because the center is
// not contained with the cell.
continue
}
// The Norm2() factor is necessary because "edge" is not normalized.
if dot*dot > sin2Angle*edge.Norm2() {
return false
}
// Otherwise, the great circle containing this edge intersects the interior of the cap. We just
// need to check whether the point of closest approach occurs between the two edge endpoints.
dir := edge.Cross(c.center.Vector)
if dir.Dot(vertices[k].Vector) < 0 && dir.Dot(vertices[(k+1)&3].Vector) > 0 {
return true
}
}
return false
}
// CellUnionBound computes a covering of the Cap. In general the covering
// consists of at most 4 cells except for very large caps, which may need
// up to 6 cells. The output is not sorted.
func (c Cap) CellUnionBound() []CellID {
// TODO(roberts): The covering could be made quite a bit tighter by mapping
// the cap to a rectangle in (i,j)-space and finding a covering for that.
// Find the maximum level such that the cap contains at most one cell vertex
// and such that CellID.AppendVertexNeighbors() can be called.
level := MinWidthMetric.MaxLevel(c.Radius().Radians()) - 1
// If level < 0, more than three face cells are required.
if level < 0 {
cellIDs := make([]CellID, 6)
for face := 0; face < 6; face++ {
cellIDs[face] = CellIDFromFace(face)
}
return cellIDs
}
// The covering consists of the 4 cells at the given level that share the
// cell vertex that is closest to the cap center.
return cellIDFromPoint(c.center).VertexNeighbors(level)
}
// Centroid returns the true centroid of the cap multiplied by its surface area
// The result lies on the ray from the origin through the cap's center, but it
// is not unit length. Note that if you just want the "surface centroid", i.e.
// the normalized result, then it is simpler to call Center.
//
// The reason for multiplying the result by the cap area is to make it
// easier to compute the centroid of more complicated shapes. The centroid
// of a union of disjoint regions can be computed simply by adding their
// Centroid() results. Caveat: for caps that contain a single point
// (i.e., zero radius), this method always returns the origin (0, 0, 0).
// This is because shapes with no area don't affect the centroid of a
// union whose total area is positive.
func (c Cap) Centroid() Point {
// From symmetry, the centroid of the cap must be somewhere on the line
// from the origin to the center of the cap on the surface of the sphere.
// When a sphere is divided into slices of constant thickness by a set of
// parallel planes, all slices have the same surface area. This implies
// that the radial component of the centroid is simply the midpoint of the
// range of radial distances spanned by the cap. That is easily computed
// from the cap height.
if c.IsEmpty() {
return Point{}
}
r := 1 - 0.5*c.Height()
return Point{c.center.Mul(r * c.Area())}
}
// Union returns the smallest cap which encloses this cap and other.
func (c Cap) Union(other Cap) Cap {
// If the other cap is larger, swap c and other for the rest of the computations.
if c.radius < other.radius {
c, other = other, c
}
if c.IsFull() || other.IsEmpty() {
return c
}
// TODO: This calculation would be more efficient using s1.ChordAngles.
cRadius := c.Radius()
otherRadius := other.Radius()
distance := c.center.Distance(other.center)
if cRadius >= distance+otherRadius {
return c
}
resRadius := 0.5 * (distance + cRadius + otherRadius)
resCenter := InterpolateAtDistance(0.5*(distance-cRadius+otherRadius), c.center, other.center)
return CapFromCenterAngle(resCenter, resRadius)
}
// Encode encodes the Cap.
func (c Cap) Encode(w io.Writer) error {
e := &encoder{w: w}
c.encode(e)
return e.err
}
func (c Cap) encode(e *encoder) {
e.writeFloat64(c.center.X)
e.writeFloat64(c.center.Y)
e.writeFloat64(c.center.Z)
e.writeFloat64(float64(c.radius))
}
// Decode decodes the Cap.
func (c *Cap) Decode(r io.Reader) error {
d := &decoder{r: asByteReader(r)}
c.decode(d)
return d.err
}
func (c *Cap) decode(d *decoder) {
c.center.X = d.readFloat64()
c.center.Y = d.readFloat64()
c.center.Z = d.readFloat64()
c.radius = s1.ChordAngle(d.readFloat64())
}

View file

@ -1,698 +0,0 @@
// Copyright 2014 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 (
"io"
"math"
"github.com/golang/geo/r1"
"github.com/golang/geo/r2"
"github.com/golang/geo/r3"
"github.com/golang/geo/s1"
)
// Cell is an S2 region object that represents a cell. Unlike CellIDs,
// it supports efficient containment and intersection tests. However, it is
// also a more expensive representation.
type Cell struct {
face int8
level int8
orientation int8
id CellID
uv r2.Rect
}
// CellFromCellID constructs a Cell corresponding to the given CellID.
func CellFromCellID(id CellID) Cell {
c := Cell{}
c.id = id
f, i, j, o := c.id.faceIJOrientation()
c.face = int8(f)
c.level = int8(c.id.Level())
c.orientation = int8(o)
c.uv = ijLevelToBoundUV(i, j, int(c.level))
return c
}
// CellFromPoint constructs a cell for the given Point.
func CellFromPoint(p Point) Cell {
return CellFromCellID(cellIDFromPoint(p))
}
// CellFromLatLng constructs a cell for the given LatLng.
func CellFromLatLng(ll LatLng) Cell {
return CellFromCellID(CellIDFromLatLng(ll))
}
// Face returns the face this cell is on.
func (c Cell) Face() int {
return int(c.face)
}
// oppositeFace returns the face opposite the given face.
func oppositeFace(face int) int {
return (face + 3) % 6
}
// Level returns the level of this cell.
func (c Cell) Level() int {
return int(c.level)
}
// ID returns the CellID this cell represents.
func (c Cell) ID() CellID {
return c.id
}
// IsLeaf returns whether this Cell is a leaf or not.
func (c Cell) IsLeaf() bool {
return c.level == maxLevel
}
// SizeIJ returns the edge length of this cell in (i,j)-space.
func (c Cell) SizeIJ() int {
return sizeIJ(int(c.level))
}
// SizeST returns the edge length of this cell in (s,t)-space.
func (c Cell) SizeST() float64 {
return c.id.sizeST(int(c.level))
}
// Vertex returns the k-th vertex of the cell (k = 0,1,2,3) in CCW order
// (lower left, lower right, upper right, upper left in the UV plane).
func (c Cell) Vertex(k int) Point {
return Point{faceUVToXYZ(int(c.face), c.uv.Vertices()[k].X, c.uv.Vertices()[k].Y).Normalize()}
}
// Edge returns the inward-facing normal of the great circle passing through
// the CCW ordered edge from vertex k to vertex k+1 (mod 4) (for k = 0,1,2,3).
func (c Cell) Edge(k int) Point {
switch k {
case 0:
return Point{vNorm(int(c.face), c.uv.Y.Lo).Normalize()} // Bottom
case 1:
return Point{uNorm(int(c.face), c.uv.X.Hi).Normalize()} // Right
case 2:
return Point{vNorm(int(c.face), c.uv.Y.Hi).Mul(-1.0).Normalize()} // Top
default:
return Point{uNorm(int(c.face), c.uv.X.Lo).Mul(-1.0).Normalize()} // Left
}
}
// BoundUV returns the bounds of this cell in (u,v)-space.
func (c Cell) BoundUV() r2.Rect {
return c.uv
}
// Center returns the direction vector corresponding to the center in
// (s,t)-space of the given cell. This is the point at which the cell is
// divided into four subcells; it is not necessarily the centroid of the
// cell in (u,v)-space or (x,y,z)-space
func (c Cell) Center() Point {
return Point{c.id.rawPoint().Normalize()}
}
// Children returns the four direct children of this cell in traversal order
// and returns true. If this is a leaf cell, or the children could not be created,
// false is returned.
// The C++ method is called Subdivide.
func (c Cell) Children() ([4]Cell, bool) {
var children [4]Cell
if c.id.IsLeaf() {
return children, false
}
// Compute the cell midpoint in uv-space.
uvMid := c.id.centerUV()
// Create four children with the appropriate bounds.
cid := c.id.ChildBegin()
for pos := 0; pos < 4; pos++ {
children[pos] = Cell{
face: c.face,
level: c.level + 1,
orientation: c.orientation ^ int8(posToOrientation[pos]),
id: cid,
}
// We want to split the cell in half in u and v. To decide which
// side to set equal to the midpoint value, we look at cell's (i,j)
// position within its parent. The index for i is in bit 1 of ij.
ij := posToIJ[c.orientation][pos]
i := ij >> 1
j := ij & 1
if i == 1 {
children[pos].uv.X.Hi = c.uv.X.Hi
children[pos].uv.X.Lo = uvMid.X
} else {
children[pos].uv.X.Lo = c.uv.X.Lo
children[pos].uv.X.Hi = uvMid.X
}
if j == 1 {
children[pos].uv.Y.Hi = c.uv.Y.Hi
children[pos].uv.Y.Lo = uvMid.Y
} else {
children[pos].uv.Y.Lo = c.uv.Y.Lo
children[pos].uv.Y.Hi = uvMid.Y
}
cid = cid.Next()
}
return children, true
}
// ExactArea returns the area of this cell as accurately as possible.
func (c Cell) ExactArea() float64 {
v0, v1, v2, v3 := c.Vertex(0), c.Vertex(1), c.Vertex(2), c.Vertex(3)
return PointArea(v0, v1, v2) + PointArea(v0, v2, v3)
}
// ApproxArea returns the approximate area of this cell. This method is accurate
// to within 3% percent for all cell sizes and accurate to within 0.1% for cells
// at level 5 or higher (i.e. squares 350km to a side or smaller on the Earth's
// surface). It is moderately cheap to compute.
func (c Cell) ApproxArea() float64 {
// All cells at the first two levels have the same area.
if c.level < 2 {
return c.AverageArea()
}
// First, compute the approximate area of the cell when projected
// perpendicular to its normal. The cross product of its diagonals gives
// the normal, and the length of the normal is twice the projected area.
flatArea := 0.5 * (c.Vertex(2).Sub(c.Vertex(0).Vector).
Cross(c.Vertex(3).Sub(c.Vertex(1).Vector)).Norm())
// Now, compensate for the curvature of the cell surface by pretending
// that the cell is shaped like a spherical cap. The ratio of the
// area of a spherical cap to the area of its projected disc turns out
// to be 2 / (1 + sqrt(1 - r*r)) where r is the radius of the disc.
// For example, when r=0 the ratio is 1, and when r=1 the ratio is 2.
// Here we set Pi*r*r == flatArea to find the equivalent disc.
return flatArea * 2 / (1 + math.Sqrt(1-math.Min(1/math.Pi*flatArea, 1)))
}
// AverageArea returns the average area of cells at the level of this cell.
// This is accurate to within a factor of 1.7.
func (c Cell) AverageArea() float64 {
return AvgAreaMetric.Value(int(c.level))
}
// IntersectsCell reports whether the intersection of this cell and the other cell is not nil.
func (c Cell) IntersectsCell(oc Cell) bool {
return c.id.Intersects(oc.id)
}
// ContainsCell reports whether this cell contains the other cell.
func (c Cell) ContainsCell(oc Cell) bool {
return c.id.Contains(oc.id)
}
// CellUnionBound computes a covering of the Cell.
func (c Cell) CellUnionBound() []CellID {
return c.CapBound().CellUnionBound()
}
// latitude returns the latitude of the cell vertex in radians given by (i,j),
// where i and j indicate the Hi (1) or Lo (0) corner.
func (c Cell) latitude(i, j int) float64 {
var u, v float64
switch {
case i == 0 && j == 0:
u = c.uv.X.Lo
v = c.uv.Y.Lo
case i == 0 && j == 1:
u = c.uv.X.Lo
v = c.uv.Y.Hi
case i == 1 && j == 0:
u = c.uv.X.Hi
v = c.uv.Y.Lo
case i == 1 && j == 1:
u = c.uv.X.Hi
v = c.uv.Y.Hi
default:
panic("i and/or j is out of bounds")
}
return latitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
}
// longitude returns the longitude of the cell vertex in radians given by (i,j),
// where i and j indicate the Hi (1) or Lo (0) corner.
func (c Cell) longitude(i, j int) float64 {
var u, v float64
switch {
case i == 0 && j == 0:
u = c.uv.X.Lo
v = c.uv.Y.Lo
case i == 0 && j == 1:
u = c.uv.X.Lo
v = c.uv.Y.Hi
case i == 1 && j == 0:
u = c.uv.X.Hi
v = c.uv.Y.Lo
case i == 1 && j == 1:
u = c.uv.X.Hi
v = c.uv.Y.Hi
default:
panic("i and/or j is out of bounds")
}
return longitude(Point{faceUVToXYZ(int(c.face), u, v)}).Radians()
}
var (
poleMinLat = math.Asin(math.Sqrt(1.0/3)) - 0.5*dblEpsilon
)
// RectBound returns the bounding rectangle of this cell.
func (c Cell) RectBound() Rect {
if c.level > 0 {
// Except for cells at level 0, the latitude and longitude extremes are
// attained at the vertices. Furthermore, the latitude range is
// determined by one pair of diagonally opposite vertices and the
// longitude range is determined by the other pair.
//
// We first determine which corner (i,j) of the cell has the largest
// absolute latitude. To maximize latitude, we want to find the point in
// the cell that has the largest absolute z-coordinate and the smallest
// absolute x- and y-coordinates. To do this we look at each coordinate
// (u and v), and determine whether we want to minimize or maximize that
// coordinate based on the axis direction and the cell's (u,v) quadrant.
u := c.uv.X.Lo + c.uv.X.Hi
v := c.uv.Y.Lo + c.uv.Y.Hi
var i, j int
if uAxis(int(c.face)).Z == 0 {
if u < 0 {
i = 1
}
} else if u > 0 {
i = 1
}
if vAxis(int(c.face)).Z == 0 {
if v < 0 {
j = 1
}
} else if v > 0 {
j = 1
}
lat := r1.IntervalFromPoint(c.latitude(i, j)).AddPoint(c.latitude(1-i, 1-j))
lng := s1.EmptyInterval().AddPoint(c.longitude(i, 1-j)).AddPoint(c.longitude(1-i, j))
// We grow the bounds slightly to make sure that the bounding rectangle
// contains LatLngFromPoint(P) for any point P inside the loop L defined by the
// four *normalized* vertices. Note that normalization of a vector can
// change its direction by up to 0.5 * dblEpsilon radians, and it is not
// enough just to add Normalize calls to the code above because the
// latitude/longitude ranges are not necessarily determined by diagonally
// opposite vertex pairs after normalization.
//
// We would like to bound the amount by which the latitude/longitude of a
// contained point P can exceed the bounds computed above. In the case of
// longitude, the normalization error can change the direction of rounding
// leading to a maximum difference in longitude of 2 * dblEpsilon. In
// the case of latitude, the normalization error can shift the latitude by
// up to 0.5 * dblEpsilon and the other sources of error can cause the
// two latitudes to differ by up to another 1.5 * dblEpsilon, which also
// leads to a maximum difference of 2 * dblEpsilon.
return Rect{lat, lng}.expanded(LatLng{s1.Angle(2 * dblEpsilon), s1.Angle(2 * dblEpsilon)}).PolarClosure()
}
// The 4 cells around the equator extend to +/-45 degrees latitude at the
// midpoints of their top and bottom edges. The two cells covering the
// poles extend down to +/-35.26 degrees at their vertices. The maximum
// error in this calculation is 0.5 * dblEpsilon.
var bound Rect
switch c.face {
case 0:
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{-math.Pi / 4, math.Pi / 4}}
case 1:
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{math.Pi / 4, 3 * math.Pi / 4}}
case 2:
bound = Rect{r1.Interval{poleMinLat, math.Pi / 2}, s1.FullInterval()}
case 3:
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{3 * math.Pi / 4, -3 * math.Pi / 4}}
case 4:
bound = Rect{r1.Interval{-math.Pi / 4, math.Pi / 4}, s1.Interval{-3 * math.Pi / 4, -math.Pi / 4}}
default:
bound = Rect{r1.Interval{-math.Pi / 2, -poleMinLat}, s1.FullInterval()}
}
// Finally, we expand the bound to account for the error when a point P is
// converted to an LatLng to test for containment. (The bound should be
// large enough so that it contains the computed LatLng of any contained
// point, not just the infinite-precision version.) We don't need to expand
// longitude because longitude is calculated via a single call to math.Atan2,
// which is guaranteed to be semi-monotonic.
return bound.expanded(LatLng{s1.Angle(dblEpsilon), s1.Angle(0)})
}
// CapBound returns the bounding cap of this cell.
func (c Cell) CapBound() Cap {
// We use the cell center in (u,v)-space as the cap axis. This vector is very close
// to GetCenter() and faster to compute. Neither one of these vectors yields the
// bounding cap with minimal surface area, but they are both pretty close.
cap := CapFromPoint(Point{faceUVToXYZ(int(c.face), c.uv.Center().X, c.uv.Center().Y).Normalize()})
for k := 0; k < 4; k++ {
cap = cap.AddPoint(c.Vertex(k))
}
return cap
}
// ContainsPoint reports whether this cell contains the given point. Note that
// unlike Loop/Polygon, a Cell is considered to be a closed set. This means
// that a point on a Cell's edge or vertex belong to the Cell and the relevant
// adjacent Cells too.
//
// If you want every point to be contained by exactly one Cell,
// you will need to convert the Cell to a Loop.
func (c Cell) ContainsPoint(p Point) bool {
var uv r2.Point
var ok bool
if uv.X, uv.Y, ok = faceXYZToUV(int(c.face), p); !ok {
return false
}
// Expand the (u,v) bound to ensure that
//
// CellFromPoint(p).ContainsPoint(p)
//
// is always true. To do this, we need to account for the error when
// converting from (u,v) coordinates to (s,t) coordinates. In the
// normal case the total error is at most dblEpsilon.
return c.uv.ExpandedByMargin(dblEpsilon).ContainsPoint(uv)
}
// Encode encodes the Cell.
func (c Cell) Encode(w io.Writer) error {
e := &encoder{w: w}
c.encode(e)
return e.err
}
func (c Cell) encode(e *encoder) {
c.id.encode(e)
}
// Decode decodes the Cell.
func (c *Cell) Decode(r io.Reader) error {
d := &decoder{r: asByteReader(r)}
c.decode(d)
return d.err
}
func (c *Cell) decode(d *decoder) {
c.id.decode(d)
*c = CellFromCellID(c.id)
}
// vertexChordDist2 returns the squared chord distance from point P to the
// given corner vertex specified by the Hi or Lo values of each.
func (c Cell) vertexChordDist2(p Point, xHi, yHi bool) s1.ChordAngle {
x := c.uv.X.Lo
y := c.uv.Y.Lo
if xHi {
x = c.uv.X.Hi
}
if yHi {
y = c.uv.Y.Hi
}
return ChordAngleBetweenPoints(p, PointFromCoords(x, y, 1))
}
// uEdgeIsClosest reports whether a point P is closer to the interior of the specified
// Cell edge (either the lower or upper edge of the Cell) or to the endpoints.
func (c Cell) uEdgeIsClosest(p Point, vHi bool) bool {
u0 := c.uv.X.Lo
u1 := c.uv.X.Hi
v := c.uv.Y.Lo
if vHi {
v = c.uv.Y.Hi
}
// These are the normals to the planes that are perpendicular to the edge
// and pass through one of its two endpoints.
dir0 := r3.Vector{v*v + 1, -u0 * v, -u0}
dir1 := r3.Vector{v*v + 1, -u1 * v, -u1}
return p.Dot(dir0) > 0 && p.Dot(dir1) < 0
}
// vEdgeIsClosest reports whether a point P is closer to the interior of the specified
// Cell edge (either the right or left edge of the Cell) or to the endpoints.
func (c Cell) vEdgeIsClosest(p Point, uHi bool) bool {
v0 := c.uv.Y.Lo
v1 := c.uv.Y.Hi
u := c.uv.X.Lo
if uHi {
u = c.uv.X.Hi
}
dir0 := r3.Vector{-u * v0, u*u + 1, -v0}
dir1 := r3.Vector{-u * v1, u*u + 1, -v1}
return p.Dot(dir0) > 0 && p.Dot(dir1) < 0
}
// edgeDistance reports the distance from a Point P to a given Cell edge. The point
// P is given by its dot product, and the uv edge by its normal in the
// given coordinate value.
func edgeDistance(ij, uv float64) s1.ChordAngle {
// Let P by the target point and let R be the closest point on the given
// edge AB. The desired distance PR can be expressed as PR^2 = PQ^2 + QR^2
// where Q is the point P projected onto the plane through the great circle
// through AB. We can compute the distance PQ^2 perpendicular to the plane
// from "dirIJ" (the dot product of the target point P with the edge
// normal) and the squared length the edge normal (1 + uv**2).
pq2 := (ij * ij) / (1 + uv*uv)
// We can compute the distance QR as (1 - OQ) where O is the sphere origin,
// and we can compute OQ^2 = 1 - PQ^2 using the Pythagorean theorem.
// (This calculation loses accuracy as angle POQ approaches Pi/2.)
qr := 1 - math.Sqrt(1-pq2)
return s1.ChordAngleFromSquaredLength(pq2 + qr*qr)
}
// distanceInternal reports the distance from the given point to the interior of
// the cell if toInterior is true or to the boundary of the cell otherwise.
func (c Cell) distanceInternal(targetXYZ Point, toInterior bool) s1.ChordAngle {
// All calculations are done in the (u,v,w) coordinates of this cell's face.
target := faceXYZtoUVW(int(c.face), targetXYZ)
// Compute dot products with all four upward or rightward-facing edge
// normals. dirIJ is the dot product for the edge corresponding to axis
// I, endpoint J. For example, dir01 is the right edge of the Cell
// (corresponding to the upper endpoint of the u-axis).
dir00 := target.X - target.Z*c.uv.X.Lo
dir01 := target.X - target.Z*c.uv.X.Hi
dir10 := target.Y - target.Z*c.uv.Y.Lo
dir11 := target.Y - target.Z*c.uv.Y.Hi
inside := true
if dir00 < 0 {
inside = false // Target is to the left of the cell
if c.vEdgeIsClosest(target, false) {
return edgeDistance(-dir00, c.uv.X.Lo)
}
}
if dir01 > 0 {
inside = false // Target is to the right of the cell
if c.vEdgeIsClosest(target, true) {
return edgeDistance(dir01, c.uv.X.Hi)
}
}
if dir10 < 0 {
inside = false // Target is below the cell
if c.uEdgeIsClosest(target, false) {
return edgeDistance(-dir10, c.uv.Y.Lo)
}
}
if dir11 > 0 {
inside = false // Target is above the cell
if c.uEdgeIsClosest(target, true) {
return edgeDistance(dir11, c.uv.Y.Hi)
}
}
if inside {
if toInterior {
return s1.ChordAngle(0)
}
// Although you might think of Cells as rectangles, they are actually
// arbitrary quadrilaterals after they are projected onto the sphere.
// Therefore the simplest approach is just to find the minimum distance to
// any of the four edges.
return minChordAngle(edgeDistance(-dir00, c.uv.X.Lo),
edgeDistance(dir01, c.uv.X.Hi),
edgeDistance(-dir10, c.uv.Y.Lo),
edgeDistance(dir11, c.uv.Y.Hi))
}
// Otherwise, the closest point is one of the four cell vertices. Note that
// it is *not* trivial to narrow down the candidates based on the edge sign
// tests above, because (1) the edges don't meet at right angles and (2)
// there are points on the far side of the sphere that are both above *and*
// below the cell, etc.
return minChordAngle(c.vertexChordDist2(target, false, false),
c.vertexChordDist2(target, true, false),
c.vertexChordDist2(target, false, true),
c.vertexChordDist2(target, true, true))
}
// Distance reports the distance from the cell to the given point. Returns zero if
// the point is inside the cell.
func (c Cell) Distance(target Point) s1.ChordAngle {
return c.distanceInternal(target, true)
}
// MaxDistance reports the maximum distance from the cell (including its interior) to the
// given point.
func (c Cell) MaxDistance(target Point) s1.ChordAngle {
// First check the 4 cell vertices. If all are within the hemisphere
// centered around target, the max distance will be to one of these vertices.
targetUVW := faceXYZtoUVW(int(c.face), target)
maxDist := maxChordAngle(c.vertexChordDist2(targetUVW, false, false),
c.vertexChordDist2(targetUVW, true, false),
c.vertexChordDist2(targetUVW, false, true),
c.vertexChordDist2(targetUVW, true, true))
if maxDist <= s1.RightChordAngle {
return maxDist
}
// Otherwise, find the minimum distance dMin to the antipodal point and the
// maximum distance will be pi - dMin.
return s1.StraightChordAngle - c.BoundaryDistance(Point{target.Mul(-1)})
}
// BoundaryDistance reports the distance from the cell boundary to the given point.
func (c Cell) BoundaryDistance(target Point) s1.ChordAngle {
return c.distanceInternal(target, false)
}
// DistanceToEdge returns the minimum distance from the cell to the given edge AB. Returns
// zero if the edge intersects the cell interior.
func (c Cell) DistanceToEdge(a, b Point) s1.ChordAngle {
// Possible optimizations:
// - Currently the (cell vertex, edge endpoint) distances are computed
// twice each, and the length of AB is computed 4 times.
// - To fix this, refactor GetDistance(target) so that it skips calculating
// the distance to each cell vertex. Instead, compute the cell vertices
// and distances in this function, and add a low-level UpdateMinDistance
// that allows the XA, XB, and AB distances to be passed in.
// - It might also be more efficient to do all calculations in UVW-space,
// since this would involve transforming 2 points rather than 4.
// First, check the minimum distance to the edge endpoints A and B.
// (This also detects whether either endpoint is inside the cell.)
minDist := minChordAngle(c.Distance(a), c.Distance(b))
if minDist == 0 {
return minDist
}
// Otherwise, check whether the edge crosses the cell boundary.
crosser := NewChainEdgeCrosser(a, b, c.Vertex(3))
for i := 0; i < 4; i++ {
if crosser.ChainCrossingSign(c.Vertex(i)) != DoNotCross {
return 0
}
}
// Finally, check whether the minimum distance occurs between a cell vertex
// and the interior of the edge AB. (Some of this work is redundant, since
// it also checks the distance to the endpoints A and B again.)
//
// Note that we don't need to check the distance from the interior of AB to
// the interior of a cell edge, because the only way that this distance can
// be minimal is if the two edges cross (already checked above).
for i := 0; i < 4; i++ {
minDist, _ = UpdateMinDistance(c.Vertex(i), a, b, minDist)
}
return minDist
}
// MaxDistanceToEdge returns the maximum distance from the cell (including its interior)
// to the given edge AB.
func (c Cell) MaxDistanceToEdge(a, b Point) s1.ChordAngle {
// If the maximum distance from both endpoints to the cell is less than π/2
// then the maximum distance from the edge to the cell is the maximum of the
// two endpoint distances.
maxDist := maxChordAngle(c.MaxDistance(a), c.MaxDistance(b))
if maxDist <= s1.RightChordAngle {
return maxDist
}
return s1.StraightChordAngle - c.DistanceToEdge(Point{a.Mul(-1)}, Point{b.Mul(-1)})
}
// DistanceToCell returns the minimum distance from this cell to the given cell.
// It returns zero if one cell contains the other.
func (c Cell) DistanceToCell(target Cell) s1.ChordAngle {
// If the cells intersect, the distance is zero. We use the (u,v) ranges
// rather than CellID intersects so that cells that share a partial edge or
// corner are considered to intersect.
if c.face == target.face && c.uv.Intersects(target.uv) {
return 0
}
// Otherwise, the minimum distance always occurs between a vertex of one
// cell and an edge of the other cell (including the edge endpoints). This
// represents a total of 32 possible (vertex, edge) pairs.
//
// TODO(roberts): This could be optimized to be at least 5x faster by pruning
// the set of possible closest vertex/edge pairs using the faces and (u,v)
// ranges of both cells.
var va, vb [4]Point
for i := 0; i < 4; i++ {
va[i] = c.Vertex(i)
vb[i] = target.Vertex(i)
}
minDist := s1.InfChordAngle()
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
minDist, _ = UpdateMinDistance(va[i], vb[j], vb[(j+1)&3], minDist)
minDist, _ = UpdateMinDistance(vb[i], va[j], va[(j+1)&3], minDist)
}
}
return minDist
}
// MaxDistanceToCell returns the maximum distance from the cell (including its
// interior) to the given target cell.
func (c Cell) MaxDistanceToCell(target Cell) s1.ChordAngle {
// Need to check the antipodal target for intersection with the cell. If it
// intersects, the distance is the straight ChordAngle.
// antipodalUV is the transpose of the original UV, interpreted within the opposite face.
antipodalUV := r2.Rect{target.uv.Y, target.uv.X}
if int(c.face) == oppositeFace(int(target.face)) && c.uv.Intersects(antipodalUV) {
return s1.StraightChordAngle
}
// Otherwise, the maximum distance always occurs between a vertex of one
// cell and an edge of the other cell (including the edge endpoints). This
// represents a total of 32 possible (vertex, edge) pairs.
//
// TODO(roberts): When the maximum distance is at most π/2, the maximum is
// always attained between a pair of vertices, and this could be made much
// faster by testing each vertex pair once rather than the current 4 times.
var va, vb [4]Point
for i := 0; i < 4; i++ {
va[i] = c.Vertex(i)
vb[i] = target.Vertex(i)
}
maxDist := s1.NegativeChordAngle
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
maxDist, _ = UpdateMaxDistance(va[i], vb[j], vb[(j+1)&3], maxDist)
maxDist, _ = UpdateMaxDistance(vb[i], va[j], va[(j+1)&3], maxDist)
}
}
return maxDist
}

View file

@ -1,921 +0,0 @@
// Copyright 2014 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 (
"bytes"
"fmt"
"io"
"math"
"sort"
"strconv"
"strings"
"github.com/golang/geo/r1"
"github.com/golang/geo/r2"
"github.com/golang/geo/r3"
"github.com/golang/geo/s1"
)
// CellID uniquely identifies a cell in the S2 cell decomposition.
// The most significant 3 bits encode the face number (0-5). The
// remaining 61 bits encode the position of the center of this cell
// along the Hilbert curve on that face. The zero value and the value
// (1<<64)-1 are invalid cell IDs. The first compares less than any
// valid cell ID, the second as greater than any valid cell ID.
//
// Sequentially increasing cell IDs follow a continuous space-filling curve
// over the entire sphere. They have the following properties:
//
// - The ID of a cell at level k consists of a 3-bit face number followed
// by k bit pairs that recursively select one of the four children of
// each cell. The next bit is always 1, and all other bits are 0.
// Therefore, the level of a cell is determined by the position of its
// lowest-numbered bit that is turned on (for a cell at level k, this
// position is 2 * (maxLevel - k)).
//
// - The ID of a parent cell is at the midpoint of the range of IDs spanned
// by its children (or by its descendants at any level).
//
// Leaf cells are often used to represent points on the unit sphere, and
// this type provides methods for converting directly between these two
// representations. For cells that represent 2D regions rather than
// discrete point, it is better to use Cells.
type CellID uint64
// SentinelCellID is an invalid cell ID guaranteed to be larger than any
// valid cell ID. It is used primarily by ShapeIndex. The value is also used
// by some S2 types when encoding data.
// Note that the sentinel's RangeMin == RangeMax == itself.
const SentinelCellID = CellID(^uint64(0))
// sortCellIDs sorts the slice of CellIDs in place.
func sortCellIDs(ci []CellID) {
sort.Sort(cellIDs(ci))
}
// cellIDs implements the Sort interface for slices of CellIDs.
type cellIDs []CellID
func (c cellIDs) Len() int { return len(c) }
func (c cellIDs) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c cellIDs) Less(i, j int) bool { return c[i] < c[j] }
// TODO(dsymonds): Some of these constants should probably be exported.
const (
faceBits = 3
numFaces = 6
// This is the number of levels needed to specify a leaf cell.
maxLevel = 30
// The extra position bit (61 rather than 60) lets us encode each cell as its
// Hilbert curve position at the cell center (which is halfway along the
// portion of the Hilbert curve that fills that cell).
posBits = 2*maxLevel + 1
// The maximum index of a valid leaf cell plus one. The range of valid leaf
// cell indices is [0..maxSize-1].
maxSize = 1 << maxLevel
wrapOffset = uint64(numFaces) << posBits
)
// CellIDFromFacePosLevel returns a cell given its face in the range
// [0,5], the 61-bit Hilbert curve position pos within that face, and
// the level in the range [0,maxLevel]. The position in the cell ID
// will be truncated to correspond to the Hilbert curve position at
// the center of the returned cell.
func CellIDFromFacePosLevel(face int, pos uint64, level int) CellID {
return CellID(uint64(face)<<posBits + pos | 1).Parent(level)
}
// CellIDFromFace returns the cell corresponding to a given S2 cube face.
func CellIDFromFace(face int) CellID {
return CellID((uint64(face) << posBits) + lsbForLevel(0))
}
// CellIDFromLatLng returns the leaf cell containing ll.
func CellIDFromLatLng(ll LatLng) CellID {
return cellIDFromPoint(PointFromLatLng(ll))
}
// CellIDFromToken returns a cell given a hex-encoded string of its uint64 ID.
func CellIDFromToken(s string) CellID {
if len(s) > 16 {
return CellID(0)
}
n, err := strconv.ParseUint(s, 16, 64)
if err != nil {
return CellID(0)
}
// Equivalent to right-padding string with zeros to 16 characters.
if len(s) < 16 {
n = n << (4 * uint(16-len(s)))
}
return CellID(n)
}
// ToToken returns a hex-encoded string of the uint64 cell id, with leading
// zeros included but trailing zeros stripped.
func (ci CellID) ToToken() string {
s := strings.TrimRight(fmt.Sprintf("%016x", uint64(ci)), "0")
if len(s) == 0 {
return "X"
}
return s
}
// IsValid reports whether ci represents a valid cell.
func (ci CellID) IsValid() bool {
return ci.Face() < numFaces && (ci.lsb()&0x1555555555555555 != 0)
}
// Face returns the cube face for this cell ID, in the range [0,5].
func (ci CellID) Face() int { return int(uint64(ci) >> posBits) }
// Pos returns the position along the Hilbert curve of this cell ID, in the range [0,2^posBits-1].
func (ci CellID) Pos() uint64 { return uint64(ci) & (^uint64(0) >> faceBits) }
// Level returns the subdivision level of this cell ID, in the range [0, maxLevel].
func (ci CellID) Level() int {
return maxLevel - findLSBSetNonZero64(uint64(ci))>>1
}
// IsLeaf returns whether this cell ID is at the deepest level;
// that is, the level at which the cells are smallest.
func (ci CellID) IsLeaf() bool { return uint64(ci)&1 != 0 }
// ChildPosition returns the child position (0..3) of this cell's
// ancestor at the given level, relative to its parent. The argument
// should be in the range 1..kMaxLevel. For example,
// ChildPosition(1) returns the position of this cell's level-1
// ancestor within its top-level face cell.
func (ci CellID) ChildPosition(level int) int {
return int(uint64(ci)>>uint64(2*(maxLevel-level)+1)) & 3
}
// lsbForLevel returns the lowest-numbered bit that is on for cells at the given level.
func lsbForLevel(level int) uint64 { return 1 << uint64(2*(maxLevel-level)) }
// Parent returns the cell at the given level, which must be no greater than the current level.
func (ci CellID) Parent(level int) CellID {
lsb := lsbForLevel(level)
return CellID((uint64(ci) & -lsb) | lsb)
}
// immediateParent is cheaper than Parent, but assumes !ci.isFace().
func (ci CellID) immediateParent() CellID {
nlsb := CellID(ci.lsb() << 2)
return (ci & -nlsb) | nlsb
}
// isFace returns whether this is a top-level (face) cell.
func (ci CellID) isFace() bool { return uint64(ci)&(lsbForLevel(0)-1) == 0 }
// lsb returns the least significant bit that is set.
func (ci CellID) lsb() uint64 { return uint64(ci) & -uint64(ci) }
// Children returns the four immediate children of this cell.
// If ci is a leaf cell, it returns four identical cells that are not the children.
func (ci CellID) Children() [4]CellID {
var ch [4]CellID
lsb := CellID(ci.lsb())
ch[0] = ci - lsb + lsb>>2
lsb >>= 1
ch[1] = ch[0] + lsb
ch[2] = ch[1] + lsb
ch[3] = ch[2] + lsb
return ch
}
func sizeIJ(level int) int {
return 1 << uint(maxLevel-level)
}
// EdgeNeighbors returns the four cells that are adjacent across the cell's four edges.
// Edges 0, 1, 2, 3 are in the down, right, up, left directions in the face space.
// All neighbors are guaranteed to be distinct.
func (ci CellID) EdgeNeighbors() [4]CellID {
level := ci.Level()
size := sizeIJ(level)
f, i, j, _ := ci.faceIJOrientation()
return [4]CellID{
cellIDFromFaceIJWrap(f, i, j-size).Parent(level),
cellIDFromFaceIJWrap(f, i+size, j).Parent(level),
cellIDFromFaceIJWrap(f, i, j+size).Parent(level),
cellIDFromFaceIJWrap(f, i-size, j).Parent(level),
}
}
// VertexNeighbors returns the neighboring cellIDs with vertex closest to this cell at the given level.
// (Normally there are four neighbors, but the closest vertex may only have three neighbors if it is one of
// the 8 cube vertices.)
func (ci CellID) VertexNeighbors(level int) []CellID {
halfSize := sizeIJ(level + 1)
size := halfSize << 1
f, i, j, _ := ci.faceIJOrientation()
var isame, jsame bool
var ioffset, joffset int
if i&halfSize != 0 {
ioffset = size
isame = (i + size) < maxSize
} else {
ioffset = -size
isame = (i - size) >= 0
}
if j&halfSize != 0 {
joffset = size
jsame = (j + size) < maxSize
} else {
joffset = -size
jsame = (j - size) >= 0
}
results := []CellID{
ci.Parent(level),
cellIDFromFaceIJSame(f, i+ioffset, j, isame).Parent(level),
cellIDFromFaceIJSame(f, i, j+joffset, jsame).Parent(level),
}
if isame || jsame {
results = append(results, cellIDFromFaceIJSame(f, i+ioffset, j+joffset, isame && jsame).Parent(level))
}
return results
}
// AllNeighbors returns all neighbors of this cell at the given level. Two
// cells X and Y are neighbors if their boundaries intersect but their
// interiors do not. In particular, two cells that intersect at a single
// point are neighbors. Note that for cells adjacent to a face vertex, the
// same neighbor may be returned more than once. There could be up to eight
// neighbors including the diagonal ones that share the vertex.
//
// This requires level >= ci.Level().
func (ci CellID) AllNeighbors(level int) []CellID {
var neighbors []CellID
face, i, j, _ := ci.faceIJOrientation()
// Find the coordinates of the lower left-hand leaf cell. We need to
// normalize (i,j) to a known position within the cell because level
// may be larger than this cell's level.
size := sizeIJ(ci.Level())
i &= -size
j &= -size
nbrSize := sizeIJ(level)
// We compute the top-bottom, left-right, and diagonal neighbors in one
// pass. The loop test is at the end of the loop to avoid 32-bit overflow.
for k := -nbrSize; ; k += nbrSize {
var sameFace bool
if k < 0 {
sameFace = (j+k >= 0)
} else if k >= size {
sameFace = (j+k < maxSize)
} else {
sameFace = true
// Top and bottom neighbors.
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j-nbrSize,
j-size >= 0).Parent(level))
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+k, j+size,
j+size < maxSize).Parent(level))
}
// Left, right, and diagonal neighbors.
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i-nbrSize, j+k,
sameFace && i-size >= 0).Parent(level))
neighbors = append(neighbors, cellIDFromFaceIJSame(face, i+size, j+k,
sameFace && i+size < maxSize).Parent(level))
if k >= size {
break
}
}
return neighbors
}
// RangeMin returns the minimum CellID that is contained within this cell.
func (ci CellID) RangeMin() CellID { return CellID(uint64(ci) - (ci.lsb() - 1)) }
// RangeMax returns the maximum CellID that is contained within this cell.
func (ci CellID) RangeMax() CellID { return CellID(uint64(ci) + (ci.lsb() - 1)) }
// Contains returns true iff the CellID contains oci.
func (ci CellID) Contains(oci CellID) bool {
return uint64(ci.RangeMin()) <= uint64(oci) && uint64(oci) <= uint64(ci.RangeMax())
}
// Intersects returns true iff the CellID intersects oci.
func (ci CellID) Intersects(oci CellID) bool {
return uint64(oci.RangeMin()) <= uint64(ci.RangeMax()) && uint64(oci.RangeMax()) >= uint64(ci.RangeMin())
}
// String returns the string representation of the cell ID in the form "1/3210".
func (ci CellID) String() string {
if !ci.IsValid() {
return "Invalid: " + strconv.FormatInt(int64(ci), 16)
}
var b bytes.Buffer
b.WriteByte("012345"[ci.Face()]) // values > 5 will have been picked off by !IsValid above
b.WriteByte('/')
for level := 1; level <= ci.Level(); level++ {
b.WriteByte("0123"[ci.ChildPosition(level)])
}
return b.String()
}
// Point returns the center of the s2 cell on the sphere as a Point.
// The maximum directional error in Point (compared to the exact
// mathematical result) is 1.5 * dblEpsilon radians, and the maximum length
// error is 2 * dblEpsilon (the same as Normalize).
func (ci CellID) Point() Point { return Point{ci.rawPoint().Normalize()} }
// LatLng returns the center of the s2 cell on the sphere as a LatLng.
func (ci CellID) LatLng() LatLng { return LatLngFromPoint(Point{ci.rawPoint()}) }
// ChildBegin returns the first child in a traversal of the children of this cell, in Hilbert curve order.
//
// for ci := c.ChildBegin(); ci != c.ChildEnd(); ci = ci.Next() {
// ...
// }
func (ci CellID) ChildBegin() CellID {
ol := ci.lsb()
return CellID(uint64(ci) - ol + ol>>2)
}
// ChildBeginAtLevel returns the first cell in a traversal of children a given level deeper than this cell, in
// Hilbert curve order. The given level must be no smaller than the cell's level.
// See ChildBegin for example use.
func (ci CellID) ChildBeginAtLevel(level int) CellID {
return CellID(uint64(ci) - ci.lsb() + lsbForLevel(level))
}
// ChildEnd returns the first cell after a traversal of the children of this cell in Hilbert curve order.
// The returned cell may be invalid.
func (ci CellID) ChildEnd() CellID {
ol := ci.lsb()
return CellID(uint64(ci) + ol + ol>>2)
}
// ChildEndAtLevel returns the first cell after the last child in a traversal of children a given level deeper
// than this cell, in Hilbert curve order.
// The given level must be no smaller than the cell's level.
// The returned cell may be invalid.
func (ci CellID) ChildEndAtLevel(level int) CellID {
return CellID(uint64(ci) + ci.lsb() + lsbForLevel(level))
}
// Next returns the next cell along the Hilbert curve.
// This is expected to be used with ChildBegin and ChildEnd,
// or ChildBeginAtLevel and ChildEndAtLevel.
func (ci CellID) Next() CellID {
return CellID(uint64(ci) + ci.lsb()<<1)
}
// Prev returns the previous cell along the Hilbert curve.
func (ci CellID) Prev() CellID {
return CellID(uint64(ci) - ci.lsb()<<1)
}
// NextWrap returns the next cell along the Hilbert curve, wrapping from last to
// first as necessary. This should not be used with ChildBegin and ChildEnd.
func (ci CellID) NextWrap() CellID {
n := ci.Next()
if uint64(n) < wrapOffset {
return n
}
return CellID(uint64(n) - wrapOffset)
}
// PrevWrap returns the previous cell along the Hilbert curve, wrapping around from
// first to last as necessary. This should not be used with ChildBegin and ChildEnd.
func (ci CellID) PrevWrap() CellID {
p := ci.Prev()
if uint64(p) < wrapOffset {
return p
}
return CellID(uint64(p) + wrapOffset)
}
// AdvanceWrap advances or retreats the indicated number of steps along the
// Hilbert curve at the current level and returns the new position. The
// position wraps between the first and last faces as necessary.
func (ci CellID) AdvanceWrap(steps int64) CellID {
if steps == 0 {
return ci
}
// We clamp the number of steps if necessary to ensure that we do not
// advance past the End() or before the Begin() of this level.
shift := uint(2*(maxLevel-ci.Level()) + 1)
if steps < 0 {
if min := -int64(uint64(ci) >> shift); steps < min {
wrap := int64(wrapOffset >> shift)
steps %= wrap
if steps < min {
steps += wrap
}
}
} else {
// Unlike Advance(), we don't want to return End(level).
if max := int64((wrapOffset - uint64(ci)) >> shift); steps > max {
wrap := int64(wrapOffset >> shift)
steps %= wrap
if steps > max {
steps -= wrap
}
}
}
// If steps is negative, then shifting it left has undefined behavior.
// Cast to uint64 for a 2's complement answer.
return CellID(uint64(ci) + (uint64(steps) << shift))
}
// Encode encodes the CellID.
func (ci CellID) Encode(w io.Writer) error {
e := &encoder{w: w}
ci.encode(e)
return e.err
}
func (ci CellID) encode(e *encoder) {
e.writeUint64(uint64(ci))
}
// Decode encodes the CellID.
func (ci *CellID) Decode(r io.Reader) error {
d := &decoder{r: asByteReader(r)}
ci.decode(d)
return d.err
}
func (ci *CellID) decode(d *decoder) {
*ci = CellID(d.readUint64())
}
// TODO: the methods below are not exported yet. Settle on the entire API design
// before doing this. Do we want to mirror the C++ one as closely as possible?
// distanceFromBegin returns the number of steps that this cell is from the first
// node in the S2 hierarchy at our level. (i.e., FromFace(0).ChildBeginAtLevel(ci.Level())).
// The return value is always non-negative.
func (ci CellID) distanceFromBegin() int64 {
return int64(ci >> uint64(2*(maxLevel-ci.Level())+1))
}
// rawPoint returns an unnormalized r3 vector from the origin through the center
// of the s2 cell on the sphere.
func (ci CellID) rawPoint() r3.Vector {
face, si, ti := ci.faceSiTi()
return faceUVToXYZ(face, stToUV((0.5/maxSize)*float64(si)), stToUV((0.5/maxSize)*float64(ti)))
}
// faceSiTi returns the Face/Si/Ti coordinates of the center of the cell.
func (ci CellID) faceSiTi() (face int, si, ti uint32) {
face, i, j, _ := ci.faceIJOrientation()
delta := 0
if ci.IsLeaf() {
delta = 1
} else {
if (i^(int(ci)>>2))&1 != 0 {
delta = 2
}
}
return face, uint32(2*i + delta), uint32(2*j + delta)
}
// faceIJOrientation uses the global lookupIJ table to unfiddle the bits of ci.
func (ci CellID) faceIJOrientation() (f, i, j, orientation int) {
f = ci.Face()
orientation = f & swapMask
nbits := maxLevel - 7*lookupBits // first iteration
// Each iteration maps 8 bits of the Hilbert curve position into
// 4 bits of "i" and "j". The lookup table transforms a key of the
// form "ppppppppoo" to a value of the form "iiiijjjjoo", where the
// letters [ijpo] represents bits of "i", "j", the Hilbert curve
// position, and the Hilbert curve orientation respectively.
//
// On the first iteration we need to be careful to clear out the bits
// representing the cube face.
for k := 7; k >= 0; k-- {
orientation += (int(uint64(ci)>>uint64(k*2*lookupBits+1)) & ((1 << uint(2*nbits)) - 1)) << 2
orientation = lookupIJ[orientation]
i += (orientation >> (lookupBits + 2)) << uint(k*lookupBits)
j += ((orientation >> 2) & ((1 << lookupBits) - 1)) << uint(k*lookupBits)
orientation &= (swapMask | invertMask)
nbits = lookupBits // following iterations
}
// The position of a non-leaf cell at level "n" consists of a prefix of
// 2*n bits that identifies the cell, followed by a suffix of
// 2*(maxLevel-n)+1 bits of the form 10*. If n==maxLevel, the suffix is
// just "1" and has no effect. Otherwise, it consists of "10", followed
// by (maxLevel-n-1) repetitions of "00", followed by "0". The "10" has
// no effect, while each occurrence of "00" has the effect of reversing
// the swapMask bit.
if ci.lsb()&0x1111111111111110 != 0 {
orientation ^= swapMask
}
return
}
// cellIDFromFaceIJ returns a leaf cell given its cube face (range 0..5) and IJ coordinates.
func cellIDFromFaceIJ(f, i, j int) CellID {
// Note that this value gets shifted one bit to the left at the end
// of the function.
n := uint64(f) << (posBits - 1)
// Alternating faces have opposite Hilbert curve orientations; this
// is necessary in order for all faces to have a right-handed
// coordinate system.
bits := f & swapMask
// Each iteration maps 4 bits of "i" and "j" into 8 bits of the Hilbert
// curve position. The lookup table transforms a 10-bit key of the form
// "iiiijjjjoo" to a 10-bit value of the form "ppppppppoo", where the
// letters [ijpo] denote bits of "i", "j", Hilbert curve position, and
// Hilbert curve orientation respectively.
for k := 7; k >= 0; k-- {
mask := (1 << lookupBits) - 1
bits += int((i>>uint(k*lookupBits))&mask) << (lookupBits + 2)
bits += int((j>>uint(k*lookupBits))&mask) << 2
bits = lookupPos[bits]
n |= uint64(bits>>2) << (uint(k) * 2 * lookupBits)
bits &= (swapMask | invertMask)
}
return CellID(n*2 + 1)
}
func cellIDFromFaceIJWrap(f, i, j int) CellID {
// Convert i and j to the coordinates of a leaf cell just beyond the
// boundary of this face. This prevents 32-bit overflow in the case
// of finding the neighbors of a face cell.
i = clampInt(i, -1, maxSize)
j = clampInt(j, -1, maxSize)
// We want to wrap these coordinates onto the appropriate adjacent face.
// The easiest way to do this is to convert the (i,j) coordinates to (x,y,z)
// (which yields a point outside the normal face boundary), and then call
// xyzToFaceUV to project back onto the correct face.
//
// The code below converts (i,j) to (si,ti), and then (si,ti) to (u,v) using
// the linear projection (u=2*s-1 and v=2*t-1). (The code further below
// converts back using the inverse projection, s=0.5*(u+1) and t=0.5*(v+1).
// Any projection would work here, so we use the simplest.) We also clamp
// the (u,v) coordinates so that the point is barely outside the
// [-1,1]x[-1,1] face rectangle, since otherwise the reprojection step
// (which divides by the new z coordinate) might change the other
// coordinates enough so that we end up in the wrong leaf cell.
const scale = 1.0 / maxSize
limit := math.Nextafter(1, 2)
u := math.Max(-limit, math.Min(limit, scale*float64((i<<1)+1-maxSize)))
v := math.Max(-limit, math.Min(limit, scale*float64((j<<1)+1-maxSize)))
// Find the leaf cell coordinates on the adjacent face, and convert
// them to a cell id at the appropriate level.
f, u, v = xyzToFaceUV(faceUVToXYZ(f, u, v))
return cellIDFromFaceIJ(f, stToIJ(0.5*(u+1)), stToIJ(0.5*(v+1)))
}
func cellIDFromFaceIJSame(f, i, j int, sameFace bool) CellID {
if sameFace {
return cellIDFromFaceIJ(f, i, j)
}
return cellIDFromFaceIJWrap(f, i, j)
}
// ijToSTMin converts the i- or j-index of a leaf cell to the minimum corresponding
// s- or t-value contained by that cell. The argument must be in the range
// [0..2**30], i.e. up to one position beyond the normal range of valid leaf
// cell indices.
func ijToSTMin(i int) float64 {
return float64(i) / float64(maxSize)
}
// stToIJ converts value in ST coordinates to a value in IJ coordinates.
func stToIJ(s float64) int {
return clampInt(int(math.Floor(maxSize*s)), 0, maxSize-1)
}
// cellIDFromPoint returns a leaf cell containing point p. Usually there is
// exactly one such cell, but for points along the edge of a cell, any
// adjacent cell may be (deterministically) chosen. This is because
// s2.CellIDs are considered to be closed sets. The returned cell will
// always contain the given point, i.e.
//
// CellFromPoint(p).ContainsPoint(p)
//
// is always true.
func cellIDFromPoint(p Point) CellID {
f, u, v := xyzToFaceUV(r3.Vector{p.X, p.Y, p.Z})
i := stToIJ(uvToST(u))
j := stToIJ(uvToST(v))
return cellIDFromFaceIJ(f, i, j)
}
// ijLevelToBoundUV returns the bounds in (u,v)-space for the cell at the given
// level containing the leaf cell with the given (i,j)-coordinates.
func ijLevelToBoundUV(i, j, level int) r2.Rect {
cellSize := sizeIJ(level)
xLo := i & -cellSize
yLo := j & -cellSize
return r2.Rect{
X: r1.Interval{
Lo: stToUV(ijToSTMin(xLo)),
Hi: stToUV(ijToSTMin(xLo + cellSize)),
},
Y: r1.Interval{
Lo: stToUV(ijToSTMin(yLo)),
Hi: stToUV(ijToSTMin(yLo + cellSize)),
},
}
}
// Constants related to the bit mangling in the Cell ID.
const (
lookupBits = 4
swapMask = 0x01
invertMask = 0x02
)
// The following lookup tables are used to convert efficiently between an
// (i,j) cell index and the corresponding position along the Hilbert curve.
//
// lookupPos maps 4 bits of "i", 4 bits of "j", and 2 bits representing the
// orientation of the current cell into 8 bits representing the order in which
// that subcell is visited by the Hilbert curve, plus 2 bits indicating the
// new orientation of the Hilbert curve within that subcell. (Cell
// orientations are represented as combination of swapMask and invertMask.)
//
// lookupIJ is an inverted table used for mapping in the opposite
// direction.
//
// We also experimented with looking up 16 bits at a time (14 bits of position
// plus 2 of orientation) but found that smaller lookup tables gave better
// performance. (2KB fits easily in the primary cache.)
var (
ijToPos = [4][4]int{
{0, 1, 3, 2}, // canonical order
{0, 3, 1, 2}, // axes swapped
{2, 3, 1, 0}, // bits inverted
{2, 1, 3, 0}, // swapped & inverted
}
posToIJ = [4][4]int{
{0, 1, 3, 2}, // canonical order: (0,0), (0,1), (1,1), (1,0)
{0, 2, 3, 1}, // axes swapped: (0,0), (1,0), (1,1), (0,1)
{3, 2, 0, 1}, // bits inverted: (1,1), (1,0), (0,0), (0,1)
{3, 1, 0, 2}, // swapped & inverted: (1,1), (0,1), (0,0), (1,0)
}
posToOrientation = [4]int{swapMask, 0, 0, invertMask | swapMask}
lookupIJ [1 << (2*lookupBits + 2)]int
lookupPos [1 << (2*lookupBits + 2)]int
)
func init() {
initLookupCell(0, 0, 0, 0, 0, 0)
initLookupCell(0, 0, 0, swapMask, 0, swapMask)
initLookupCell(0, 0, 0, invertMask, 0, invertMask)
initLookupCell(0, 0, 0, swapMask|invertMask, 0, swapMask|invertMask)
}
// initLookupCell initializes the lookupIJ table at init time.
func initLookupCell(level, i, j, origOrientation, pos, orientation int) {
if level == lookupBits {
ij := (i << lookupBits) + j
lookupPos[(ij<<2)+origOrientation] = (pos << 2) + orientation
lookupIJ[(pos<<2)+origOrientation] = (ij << 2) + orientation
return
}
level++
i <<= 1
j <<= 1
pos <<= 2
r := posToIJ[orientation]
initLookupCell(level, i+(r[0]>>1), j+(r[0]&1), origOrientation, pos, orientation^posToOrientation[0])
initLookupCell(level, i+(r[1]>>1), j+(r[1]&1), origOrientation, pos+1, orientation^posToOrientation[1])
initLookupCell(level, i+(r[2]>>1), j+(r[2]&1), origOrientation, pos+2, orientation^posToOrientation[2])
initLookupCell(level, i+(r[3]>>1), j+(r[3]&1), origOrientation, pos+3, orientation^posToOrientation[3])
}
// CommonAncestorLevel returns the level of the common ancestor of the two S2 CellIDs.
func (ci CellID) CommonAncestorLevel(other CellID) (level int, ok bool) {
bits := uint64(ci ^ other)
if bits < ci.lsb() {
bits = ci.lsb()
}
if bits < other.lsb() {
bits = other.lsb()
}
msbPos := findMSBSetNonZero64(bits)
if msbPos > 60 {
return 0, false
}
return (60 - msbPos) >> 1, true
}
// Advance advances or retreats the indicated number of steps along the
// Hilbert curve at the current level, and returns the new position. The
// position is never advanced past End() or before Begin().
func (ci CellID) Advance(steps int64) CellID {
if steps == 0 {
return ci
}
// We clamp the number of steps if necessary to ensure that we do not
// advance past the End() or before the Begin() of this level. Note that
// minSteps and maxSteps always fit in a signed 64-bit integer.
stepShift := uint(2*(maxLevel-ci.Level()) + 1)
if steps < 0 {
minSteps := -int64(uint64(ci) >> stepShift)
if steps < minSteps {
steps = minSteps
}
} else {
maxSteps := int64((wrapOffset + ci.lsb() - uint64(ci)) >> stepShift)
if steps > maxSteps {
steps = maxSteps
}
}
return ci + CellID(steps)<<stepShift
}
// centerST return the center of the CellID in (s,t)-space.
func (ci CellID) centerST() r2.Point {
_, si, ti := ci.faceSiTi()
return r2.Point{siTiToST(si), siTiToST(ti)}
}
// sizeST returns the edge length of this CellID in (s,t)-space at the given level.
func (ci CellID) sizeST(level int) float64 {
return ijToSTMin(sizeIJ(level))
}
// boundST returns the bound of this CellID in (s,t)-space.
func (ci CellID) boundST() r2.Rect {
s := ci.sizeST(ci.Level())
return r2.RectFromCenterSize(ci.centerST(), r2.Point{s, s})
}
// centerUV returns the center of this CellID in (u,v)-space. Note that
// the center of the cell is defined as the point at which it is recursively
// subdivided into four children; in general, it is not at the midpoint of
// the (u,v) rectangle covered by the cell.
func (ci CellID) centerUV() r2.Point {
_, si, ti := ci.faceSiTi()
return r2.Point{stToUV(siTiToST(si)), stToUV(siTiToST(ti))}
}
// boundUV returns the bound of this CellID in (u,v)-space.
func (ci CellID) boundUV() r2.Rect {
_, i, j, _ := ci.faceIJOrientation()
return ijLevelToBoundUV(i, j, ci.Level())
}
// expandEndpoint returns a new u-coordinate u' such that the distance from the
// line u=u' to the given edge (u,v0)-(u,v1) is exactly the given distance
// (which is specified as the sine of the angle corresponding to the distance).
func expandEndpoint(u, maxV, sinDist float64) float64 {
// This is based on solving a spherical right triangle, similar to the
// calculation in Cap.RectBound.
// Given an edge of the form (u,v0)-(u,v1), let maxV = max(abs(v0), abs(v1)).
sinUShift := sinDist * math.Sqrt((1+u*u+maxV*maxV)/(1+u*u))
cosUShift := math.Sqrt(1 - sinUShift*sinUShift)
// The following is an expansion of tan(atan(u) + asin(sinUShift)).
return (cosUShift*u + sinUShift) / (cosUShift - sinUShift*u)
}
// expandedByDistanceUV returns a rectangle expanded in (u,v)-space so that it
// contains all points within the given distance of the boundary, and return the
// smallest such rectangle. If the distance is negative, then instead shrink this
// rectangle so that it excludes all points within the given absolute distance
// of the boundary.
//
// Distances are measured *on the sphere*, not in (u,v)-space. For example,
// you can use this method to expand the (u,v)-bound of an CellID so that
// it contains all points within 5km of the original cell. You can then
// test whether a point lies within the expanded bounds like this:
//
// if u, v, ok := faceXYZtoUV(face, point); ok && bound.ContainsPoint(r2.Point{u,v}) { ... }
//
// Limitations:
//
// - Because the rectangle is drawn on one of the six cube-face planes
// (i.e., {x,y,z} = +/-1), it can cover at most one hemisphere. This
// limits the maximum amount that a rectangle can be expanded. For
// example, CellID bounds can be expanded safely by at most 45 degrees
// (about 5000 km on the Earth's surface).
//
// - The implementation is not exact for negative distances. The resulting
// rectangle will exclude all points within the given distance of the
// boundary but may be slightly smaller than necessary.
func expandedByDistanceUV(uv r2.Rect, distance s1.Angle) r2.Rect {
// Expand each of the four sides of the rectangle just enough to include all
// points within the given distance of that side. (The rectangle may be
// expanded by a different amount in (u,v)-space on each side.)
maxU := math.Max(math.Abs(uv.X.Lo), math.Abs(uv.X.Hi))
maxV := math.Max(math.Abs(uv.Y.Lo), math.Abs(uv.Y.Hi))
sinDist := math.Sin(float64(distance))
return r2.Rect{
X: r1.Interval{expandEndpoint(uv.X.Lo, maxV, -sinDist),
expandEndpoint(uv.X.Hi, maxV, sinDist)},
Y: r1.Interval{expandEndpoint(uv.Y.Lo, maxU, -sinDist),
expandEndpoint(uv.Y.Hi, maxU, sinDist)}}
}
// MaxTile returns the largest cell with the same RangeMin such that
// RangeMax < limit.RangeMin. It returns limit if no such cell exists.
// This method can be used to generate a small set of CellIDs that covers
// a given range (a tiling). This example shows how to generate a tiling
// for a semi-open range of leaf cells [start, limit):
//
// for id := start.MaxTile(limit); id != limit; id = id.Next().MaxTile(limit)) { ... }
//
// Note that in general the cells in the tiling will be of different sizes;
// they gradually get larger (near the middle of the range) and then
// gradually get smaller as limit is approached.
func (ci CellID) MaxTile(limit CellID) CellID {
start := ci.RangeMin()
if start >= limit.RangeMin() {
return limit
}
if ci.RangeMax() >= limit {
// The cell is too large, shrink it. Note that when generating coverings
// of CellID ranges, this loop usually executes only once. Also because
// ci.RangeMin() < limit.RangeMin(), we will always exit the loop by the
// time we reach a leaf cell.
for {
ci = ci.Children()[0]
if ci.RangeMax() < limit {
break
}
}
return ci
}
// The cell may be too small. Grow it if necessary. Note that generally
// this loop only iterates once.
for !ci.isFace() {
parent := ci.immediateParent()
if parent.RangeMin() != start || parent.RangeMax() >= limit {
break
}
ci = parent
}
return ci
}
// centerFaceSiTi returns the (face, si, ti) coordinates of the center of the cell.
// Note that although (si,ti) coordinates span the range [0,2**31] in general,
// the cell center coordinates are always in the range [1,2**31-1] and
// therefore can be represented using a signed 32-bit integer.
func (ci CellID) centerFaceSiTi() (face, si, ti int) {
// First we compute the discrete (i,j) coordinates of a leaf cell contained
// within the given cell. Given that cells are represented by the Hilbert
// curve position corresponding at their center, it turns out that the cell
// returned by faceIJOrientation is always one of two leaf cells closest
// to the center of the cell (unless the given cell is a leaf cell itself,
// in which case there is only one possibility).
//
// Given a cell of size s >= 2 (i.e. not a leaf cell), and letting (imin,
// jmin) be the coordinates of its lower left-hand corner, the leaf cell
// returned by faceIJOrientation is either (imin + s/2, jmin + s/2)
// (imin + s/2 - 1, jmin + s/2 - 1). The first case is the one we want.
// We can distinguish these two cases by looking at the low bit of i or
// j. In the second case the low bit is one, unless s == 2 (i.e. the
// level just above leaf cells) in which case the low bit is zero.
//
// In the code below, the expression ((i ^ (int(id) >> 2)) & 1) is true
// if we are in the second case described above.
face, i, j, _ := ci.faceIJOrientation()
delta := 0
if ci.IsLeaf() {
delta = 1
} else if (int64(i)^(int64(ci)>>2))&1 == 1 {
delta = 2
}
// Note that (2 * {i,j} + delta) will never overflow a 32-bit integer.
return face, 2*i + delta, 2*j + delta
}

View file

@ -1,590 +0,0 @@
// Copyright 2014 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 (
"fmt"
"io"
"sort"
"github.com/golang/geo/s1"
)
// A CellUnion is a collection of CellIDs.
//
// It is normalized if it is sorted, and does not contain redundancy.
// Specifically, it may not contain the same CellID twice, nor a CellID that
// is contained by another, nor the four sibling CellIDs that are children of
// a single higher level CellID.
//
// CellUnions are not required to be normalized, but certain operations will
// return different results if they are not (e.g. Contains).
type CellUnion []CellID
// CellUnionFromRange creates a CellUnion that covers the half-open range
// of leaf cells [begin, end). If begin == end the resulting union is empty.
// This requires that begin and end are both leaves, and begin <= end.
// To create a closed-ended range, pass in end.Next().
func CellUnionFromRange(begin, end CellID) CellUnion {
// We repeatedly add the largest cell we can.
var cu CellUnion
for id := begin.MaxTile(end); id != end; id = id.Next().MaxTile(end) {
cu = append(cu, id)
}
// The output is normalized because the cells are added in order by the iteration.
return cu
}
// CellUnionFromUnion creates a CellUnion from the union of the given CellUnions.
func CellUnionFromUnion(cellUnions ...CellUnion) CellUnion {
var cu CellUnion
for _, cellUnion := range cellUnions {
cu = append(cu, cellUnion...)
}
cu.Normalize()
return cu
}
// CellUnionFromIntersection creates a CellUnion from the intersection of the given CellUnions.
func CellUnionFromIntersection(x, y CellUnion) CellUnion {
var cu CellUnion
// This is a fairly efficient calculation that uses binary search to skip
// over sections of both input vectors. It takes constant time if all the
// cells of x come before or after all the cells of y in CellID order.
var i, j int
for i < len(x) && j < len(y) {
iMin := x[i].RangeMin()
jMin := y[j].RangeMin()
if iMin > jMin {
// Either j.Contains(i) or the two cells are disjoint.
if x[i] <= y[j].RangeMax() {
cu = append(cu, x[i])
i++
} else {
// Advance j to the first cell possibly contained by x[i].
j = y.lowerBound(j+1, len(y), iMin)
// The previous cell y[j-1] may now contain x[i].
if x[i] <= y[j-1].RangeMax() {
j--
}
}
} else if jMin > iMin {
// Identical to the code above with i and j reversed.
if y[j] <= x[i].RangeMax() {
cu = append(cu, y[j])
j++
} else {
i = x.lowerBound(i+1, len(x), jMin)
if y[j] <= x[i-1].RangeMax() {
i--
}
}
} else {
// i and j have the same RangeMin(), so one contains the other.
if x[i] < y[j] {
cu = append(cu, x[i])
i++
} else {
cu = append(cu, y[j])
j++
}
}
}
// The output is generated in sorted order.
cu.Normalize()
return cu
}
// CellUnionFromIntersectionWithCellID creates a CellUnion from the intersection
// of a CellUnion with the given CellID. This can be useful for splitting a
// CellUnion into chunks.
func CellUnionFromIntersectionWithCellID(x CellUnion, id CellID) CellUnion {
var cu CellUnion
if x.ContainsCellID(id) {
cu = append(cu, id)
cu.Normalize()
return cu
}
idmax := id.RangeMax()
for i := x.lowerBound(0, len(x), id.RangeMin()); i < len(x) && x[i] <= idmax; i++ {
cu = append(cu, x[i])
}
cu.Normalize()
return cu
}
// CellUnionFromDifference creates a CellUnion from the difference (x - y)
// of the given CellUnions.
func CellUnionFromDifference(x, y CellUnion) CellUnion {
// TODO(roberts): This is approximately O(N*log(N)), but could probably
// use similar techniques as CellUnionFromIntersectionWithCellID to be more efficient.
var cu CellUnion
for _, xid := range x {
cu.cellUnionDifferenceInternal(xid, &y)
}
// The output is generated in sorted order, and there should not be any
// cells that can be merged (provided that both inputs were normalized).
return cu
}
// The C++ constructor methods FromNormalized and FromVerbatim are not necessary
// since they don't call Normalize, and just set the CellIDs directly on the object,
// so straight casting is sufficient in Go to replicate this behavior.
// IsValid reports whether the cell union is valid, meaning that the CellIDs are
// valid, non-overlapping, and sorted in increasing order.
func (cu *CellUnion) IsValid() bool {
for i, cid := range *cu {
if !cid.IsValid() {
return false
}
if i == 0 {
continue
}
if (*cu)[i-1].RangeMax() >= cid.RangeMin() {
return false
}
}
return true
}
// IsNormalized reports whether the cell union is normalized, meaning that it is
// satisfies IsValid and that no four cells have a common parent.
// Certain operations such as Contains will return a different
// result if the cell union is not normalized.
func (cu *CellUnion) IsNormalized() bool {
for i, cid := range *cu {
if !cid.IsValid() {
return false
}
if i == 0 {
continue
}
if (*cu)[i-1].RangeMax() >= cid.RangeMin() {
return false
}
if i < 3 {
continue
}
if areSiblings((*cu)[i-3], (*cu)[i-2], (*cu)[i-1], cid) {
return false
}
}
return true
}
// Normalize normalizes the CellUnion.
func (cu *CellUnion) Normalize() {
sortCellIDs(*cu)
output := make([]CellID, 0, len(*cu)) // the list of accepted cells
// Loop invariant: output is a sorted list of cells with no redundancy.
for _, ci := range *cu {
// The first two passes here either ignore this new candidate,
// or remove previously accepted cells that are covered by this candidate.
// Ignore this cell if it is contained by the previous one.
// We only need to check the last accepted cell. The ordering of the
// cells implies containment (but not the converse), and output has no redundancy,
// so if this candidate is not contained by the last accepted cell
// then it cannot be contained by any previously accepted cell.
if len(output) > 0 && output[len(output)-1].Contains(ci) {
continue
}
// Discard any previously accepted cells contained by this one.
// This could be any contiguous trailing subsequence, but it can't be
// a discontiguous subsequence because of the containment property of
// sorted S2 cells mentioned above.
j := len(output) - 1 // last index to keep
for j >= 0 {
if !ci.Contains(output[j]) {
break
}
j--
}
output = output[:j+1]
// See if the last three cells plus this one can be collapsed.
// We loop because collapsing three accepted cells and adding a higher level cell
// could cascade into previously accepted cells.
for len(output) >= 3 && areSiblings(output[len(output)-3], output[len(output)-2], output[len(output)-1], ci) {
// Replace four children by their parent cell.
output = output[:len(output)-3]
ci = ci.immediateParent() // checked !ci.isFace above
}
output = append(output, ci)
}
*cu = output
}
// IntersectsCellID reports whether this CellUnion intersects the given cell ID.
func (cu *CellUnion) IntersectsCellID(id CellID) bool {
// Find index of array item that occurs directly after our probe cell:
i := sort.Search(len(*cu), func(i int) bool { return id < (*cu)[i] })
if i != len(*cu) && (*cu)[i].RangeMin() <= id.RangeMax() {
return true
}
return i != 0 && (*cu)[i-1].RangeMax() >= id.RangeMin()
}
// ContainsCellID reports whether the CellUnion contains the given cell ID.
// Containment is defined with respect to regions, e.g. a cell contains its 4 children.
//
// CAVEAT: If you have constructed a non-normalized CellUnion, note that groups
// of 4 child cells are *not* considered to contain their parent cell. To get
// this behavior you must use one of the call Normalize() explicitly.
func (cu *CellUnion) ContainsCellID(id CellID) bool {
// Find index of array item that occurs directly after our probe cell:
i := sort.Search(len(*cu), func(i int) bool { return id < (*cu)[i] })
if i != len(*cu) && (*cu)[i].RangeMin() <= id {
return true
}
return i != 0 && (*cu)[i-1].RangeMax() >= id
}
// Denormalize replaces this CellUnion with an expanded version of the
// CellUnion where any cell whose level is less than minLevel or where
// (level - minLevel) is not a multiple of levelMod is replaced by its
// children, until either both of these conditions are satisfied or the
// maximum level is reached.
func (cu *CellUnion) Denormalize(minLevel, levelMod int) {
var denorm CellUnion
for _, id := range *cu {
level := id.Level()
newLevel := level
if newLevel < minLevel {
newLevel = minLevel
}
if levelMod > 1 {
newLevel += (maxLevel - (newLevel - minLevel)) % levelMod
if newLevel > maxLevel {
newLevel = maxLevel
}
}
if newLevel == level {
denorm = append(denorm, id)
} else {
end := id.ChildEndAtLevel(newLevel)
for ci := id.ChildBeginAtLevel(newLevel); ci != end; ci = ci.Next() {
denorm = append(denorm, ci)
}
}
}
*cu = denorm
}
// RectBound returns a Rect that bounds this entity.
func (cu *CellUnion) RectBound() Rect {
bound := EmptyRect()
for _, c := range *cu {
bound = bound.Union(CellFromCellID(c).RectBound())
}
return bound
}
// CapBound returns a Cap that bounds this entity.
func (cu *CellUnion) CapBound() Cap {
if len(*cu) == 0 {
return EmptyCap()
}
// Compute the approximate centroid of the region. This won't produce the
// bounding cap of minimal area, but it should be close enough.
var centroid Point
for _, ci := range *cu {
area := AvgAreaMetric.Value(ci.Level())
centroid = Point{centroid.Add(ci.Point().Mul(area))}
}
if zero := (Point{}); centroid == zero {
centroid = PointFromCoords(1, 0, 0)
} else {
centroid = Point{centroid.Normalize()}
}
// Use the centroid as the cap axis, and expand the cap angle so that it
// contains the bounding caps of all the individual cells. Note that it is
// *not* sufficient to just bound all the cell vertices because the bounding
// cap may be concave (i.e. cover more than one hemisphere).
c := CapFromPoint(centroid)
for _, ci := range *cu {
c = c.AddCap(CellFromCellID(ci).CapBound())
}
return c
}
// ContainsCell reports whether this cell union contains the given cell.
func (cu *CellUnion) ContainsCell(c Cell) bool {
return cu.ContainsCellID(c.id)
}
// IntersectsCell reports whether this cell union intersects the given cell.
func (cu *CellUnion) IntersectsCell(c Cell) bool {
return cu.IntersectsCellID(c.id)
}
// ContainsPoint reports whether this cell union contains the given point.
func (cu *CellUnion) ContainsPoint(p Point) bool {
return cu.ContainsCell(CellFromPoint(p))
}
// CellUnionBound computes a covering of the CellUnion.
func (cu *CellUnion) CellUnionBound() []CellID {
return cu.CapBound().CellUnionBound()
}
// LeafCellsCovered reports the number of leaf cells covered by this cell union.
// This will be no more than 6*2^60 for the whole sphere.
func (cu *CellUnion) LeafCellsCovered() int64 {
var numLeaves int64
for _, c := range *cu {
numLeaves += 1 << uint64((maxLevel-int64(c.Level()))<<1)
}
return numLeaves
}
// Returns true if the given four cells have a common parent.
// This requires that the four CellIDs are distinct.
func areSiblings(a, b, c, d CellID) bool {
// A necessary (but not sufficient) condition is that the XOR of the
// four cell IDs must be zero. This is also very fast to test.
if (a ^ b ^ c) != d {
return false
}
// Now we do a slightly more expensive but exact test. First, compute a
// mask that blocks out the two bits that encode the child position of
// "id" with respect to its parent, then check that the other three
// children all agree with "mask".
mask := uint64(d.lsb() << 1)
mask = ^(mask + (mask << 1))
idMasked := (uint64(d) & mask)
return ((uint64(a)&mask) == idMasked &&
(uint64(b)&mask) == idMasked &&
(uint64(c)&mask) == idMasked &&
!d.isFace())
}
// Contains reports whether this CellUnion contains all of the CellIDs of the given CellUnion.
func (cu *CellUnion) Contains(o CellUnion) bool {
// TODO(roberts): Investigate alternatives such as divide-and-conquer
// or alternating-skip-search that may be significantly faster in both
// the average and worst case. This applies to Intersects as well.
for _, id := range o {
if !cu.ContainsCellID(id) {
return false
}
}
return true
}
// Intersects reports whether this CellUnion intersects any of the CellIDs of the given CellUnion.
func (cu *CellUnion) Intersects(o CellUnion) bool {
for _, c := range *cu {
if o.ContainsCellID(c) {
return true
}
}
return false
}
// lowerBound returns the index in this CellUnion to the first element whose value
// is not considered to go before the given cell id. (i.e., either it is equivalent
// or comes after the given id.) If there is no match, then end is returned.
func (cu *CellUnion) lowerBound(begin, end int, id CellID) int {
for i := begin; i < end; i++ {
if (*cu)[i] >= id {
return i
}
}
return end
}
// cellUnionDifferenceInternal adds the difference between the CellID and the union to
// the result CellUnion. If they intersect but the difference is non-empty, it divides
// and conquers.
func (cu *CellUnion) cellUnionDifferenceInternal(id CellID, other *CellUnion) {
if !other.IntersectsCellID(id) {
(*cu) = append((*cu), id)
return
}
if !other.ContainsCellID(id) {
for _, child := range id.Children() {
cu.cellUnionDifferenceInternal(child, other)
}
}
}
// ExpandAtLevel expands this CellUnion by adding a rim of cells at expandLevel
// around the unions boundary.
//
// For each cell c in the union, we add all cells at level
// expandLevel that abut c. There are typically eight of those
// (four edge-abutting and four sharing a vertex). However, if c is
// finer than expandLevel, we add all cells abutting
// c.Parent(expandLevel) as well as c.Parent(expandLevel) itself,
// as an expandLevel cell rarely abuts a smaller cell.
//
// Note that the size of the output is exponential in
// expandLevel. For example, if expandLevel == 20 and the input
// has a cell at level 10, there will be on the order of 4000
// adjacent cells in the output. For most applications the
// ExpandByRadius method below is easier to use.
func (cu *CellUnion) ExpandAtLevel(level int) {
var output CellUnion
levelLsb := lsbForLevel(level)
for i := len(*cu) - 1; i >= 0; i-- {
id := (*cu)[i]
if id.lsb() < levelLsb {
id = id.Parent(level)
// Optimization: skip over any cells contained by this one. This is
// especially important when very small regions are being expanded.
for i > 0 && id.Contains((*cu)[i-1]) {
i--
}
}
output = append(output, id)
output = append(output, id.AllNeighbors(level)...)
}
sortCellIDs(output)
*cu = output
cu.Normalize()
}
// ExpandByRadius expands this CellUnion such that it contains all points whose
// distance to the CellUnion is at most minRadius, but do not use cells that
// are more than maxLevelDiff levels higher than the largest cell in the input.
// The second parameter controls the tradeoff between accuracy and output size
// when a large region is being expanded by a small amount (e.g. expanding Canada
// by 1km). For example, if maxLevelDiff == 4 the region will always be expanded
// by approximately 1/16 the width of its largest cell. Note that in the worst case,
// the number of cells in the output can be up to 4 * (1 + 2 ** maxLevelDiff) times
// larger than the number of cells in the input.
func (cu *CellUnion) ExpandByRadius(minRadius s1.Angle, maxLevelDiff int) {
minLevel := maxLevel
for _, cid := range *cu {
minLevel = minInt(minLevel, cid.Level())
}
// Find the maximum level such that all cells are at least "minRadius" wide.
radiusLevel := MinWidthMetric.MaxLevel(minRadius.Radians())
if radiusLevel == 0 && minRadius.Radians() > MinWidthMetric.Value(0) {
// The requested expansion is greater than the width of a face cell.
// The easiest way to handle this is to expand twice.
cu.ExpandAtLevel(0)
}
cu.ExpandAtLevel(minInt(minLevel+maxLevelDiff, radiusLevel))
}
// Equal reports whether the two CellUnions are equal.
func (cu CellUnion) Equal(o CellUnion) bool {
if len(cu) != len(o) {
return false
}
for i := 0; i < len(cu); i++ {
if cu[i] != o[i] {
return false
}
}
return true
}
// AverageArea returns the average area of this CellUnion.
// This is accurate to within a factor of 1.7.
func (cu *CellUnion) AverageArea() float64 {
return AvgAreaMetric.Value(maxLevel) * float64(cu.LeafCellsCovered())
}
// ApproxArea returns the approximate area of this CellUnion. This method is accurate
// to within 3% percent for all cell sizes and accurate to within 0.1% for cells
// at level 5 or higher within the union.
func (cu *CellUnion) ApproxArea() float64 {
var area float64
for _, id := range *cu {
area += CellFromCellID(id).ApproxArea()
}
return area
}
// ExactArea returns the area of this CellUnion as accurately as possible.
func (cu *CellUnion) ExactArea() float64 {
var area float64
for _, id := range *cu {
area += CellFromCellID(id).ExactArea()
}
return area
}
// Encode encodes the CellUnion.
func (cu *CellUnion) Encode(w io.Writer) error {
e := &encoder{w: w}
cu.encode(e)
return e.err
}
func (cu *CellUnion) encode(e *encoder) {
e.writeInt8(encodingVersion)
e.writeInt64(int64(len(*cu)))
for _, ci := range *cu {
ci.encode(e)
}
}
// Decode decodes the CellUnion.
func (cu *CellUnion) Decode(r io.Reader) error {
d := &decoder{r: asByteReader(r)}
cu.decode(d)
return d.err
}
func (cu *CellUnion) decode(d *decoder) {
version := d.readInt8()
if d.err != nil {
return
}
if version != encodingVersion {
d.err = fmt.Errorf("only version %d is supported", encodingVersion)
return
}
n := d.readInt64()
if d.err != nil {
return
}
const maxCells = 1000000
if n > maxCells {
d.err = fmt.Errorf("too many cells (%d; max is %d)", n, maxCells)
return
}
*cu = make([]CellID, n)
for i := range *cu {
(*cu)[i].decode(d)
}
}

View file

@ -1,133 +0,0 @@
// 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)}
}

View file

@ -1,157 +0,0 @@
// 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

@ -1,63 +0,0 @@
// Copyright 2017 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
// ContainsVertexQuery is used to track the edges entering and leaving the
// given vertex of a Polygon in order to be able to determine if the point is
// contained by the Polygon.
//
// Point containment is defined according to the semi-open boundary model
// which means that if several polygons tile the region around a vertex,
// then exactly one of those polygons contains that vertex.
type ContainsVertexQuery struct {
target Point
edgeMap map[Point]int
}
// NewContainsVertexQuery returns a new query for the given vertex whose
// containment will be determined.
func NewContainsVertexQuery(target Point) *ContainsVertexQuery {
return &ContainsVertexQuery{
target: target,
edgeMap: make(map[Point]int),
}
}
// AddEdge adds the edge between target and v with the given direction.
// (+1 = outgoing, -1 = incoming, 0 = degenerate).
func (q *ContainsVertexQuery) AddEdge(v Point, direction int) {
q.edgeMap[v] += direction
}
// ContainsVertex reports a +1 if the target vertex is contained, -1 if it is
// not contained, and 0 if the incident edges consisted of matched sibling pairs.
func (q *ContainsVertexQuery) ContainsVertex() int {
// Find the unmatched edge that is immediately clockwise from Ortho(P).
referenceDir := Point{q.target.Ortho()}
bestPoint := referenceDir
bestDir := 0
for k, v := range q.edgeMap {
if v == 0 {
continue // This is a "matched" edge.
}
if OrderedCCW(referenceDir, bestPoint, k, q.target) {
bestPoint = k
bestDir = v
}
}
return bestDir
}

View file

@ -1,410 +0,0 @@
// Copyright 2017 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 (
"sort"
"github.com/golang/geo/r2"
)
// CrossingEdgeQuery is used to find the Edge IDs of Shapes that are crossed by
// a given edge(s).
//
// Note that if you need to query many edges, it is more efficient to declare
// a single CrossingEdgeQuery instance and reuse it.
//
// If you want to find *all* the pairs of crossing edges, it is more efficient to
// use the not yet implemented VisitCrossings in shapeutil.
type CrossingEdgeQuery struct {
index *ShapeIndex
// temporary values used while processing a query.
a, b r2.Point
iter *ShapeIndexIterator
// candidate cells generated when finding crossings.
cells []*ShapeIndexCell
}
// NewCrossingEdgeQuery creates a CrossingEdgeQuery for the given index.
func NewCrossingEdgeQuery(index *ShapeIndex) *CrossingEdgeQuery {
c := &CrossingEdgeQuery{
index: index,
iter: index.Iterator(),
}
return c
}
// Crossings returns the set of edge of the shape S that intersect the given edge AB.
// If the CrossingType is Interior, then only intersections at a point interior to both
// edges are reported, while if it is CrossingTypeAll then edges that share a vertex
// are also reported.
func (c *CrossingEdgeQuery) Crossings(a, b Point, shape Shape, crossType CrossingType) []int {
edges := c.candidates(a, b, shape)
if len(edges) == 0 {
return nil
}
crosser := NewEdgeCrosser(a, b)
out := 0
n := len(edges)
for in := 0; in < n; in++ {
b := shape.Edge(edges[in])
sign := crosser.CrossingSign(b.V0, b.V1)
if crossType == CrossingTypeAll && (sign == MaybeCross || sign == Cross) || crossType != CrossingTypeAll && sign == Cross {
edges[out] = edges[in]
out++
}
}
if out < n {
edges = edges[0:out]
}
return edges
}
// EdgeMap stores a sorted set of edge ids for each shape.
type EdgeMap map[Shape][]int
// CrossingsEdgeMap returns the set of all edges in the index that intersect the given
// edge AB. If crossType is CrossingTypeInterior, then only intersections at a
// point interior to both edges are reported, while if it is CrossingTypeAll
// then edges that share a vertex are also reported.
//
// The edges are returned as a mapping from shape to the edges of that shape
// that intersect AB. Every returned shape has at least one crossing edge.
func (c *CrossingEdgeQuery) CrossingsEdgeMap(a, b Point, crossType CrossingType) EdgeMap {
edgeMap := c.candidatesEdgeMap(a, b)
if len(edgeMap) == 0 {
return nil
}
crosser := NewEdgeCrosser(a, b)
for shape, edges := range edgeMap {
out := 0
n := len(edges)
for in := 0; in < n; in++ {
edge := shape.Edge(edges[in])
sign := crosser.CrossingSign(edge.V0, edge.V1)
if (crossType == CrossingTypeAll && (sign == MaybeCross || sign == Cross)) || (crossType != CrossingTypeAll && sign == Cross) {
edgeMap[shape][out] = edges[in]
out++
}
}
if out == 0 {
delete(edgeMap, shape)
} else {
if out < n {
edgeMap[shape] = edgeMap[shape][0:out]
}
}
}
return edgeMap
}
// candidates returns a superset of the edges of the given shape that intersect
// the edge AB.
func (c *CrossingEdgeQuery) candidates(a, b Point, shape Shape) []int {
var edges []int
// For small loops it is faster to use brute force. The threshold below was
// determined using benchmarks.
const maxBruteForceEdges = 27
maxEdges := shape.NumEdges()
if maxEdges <= maxBruteForceEdges {
edges = make([]int, maxEdges)
for i := 0; i < maxEdges; i++ {
edges[i] = i
}
return edges
}
// Compute the set of index cells intersected by the query edge.
c.getCellsForEdge(a, b)
if len(c.cells) == 0 {
return nil
}
// Gather all the edges that intersect those cells and sort them.
// TODO(roberts): Shapes don't track their ID, so we need to range over
// the index to find the ID manually.
var shapeID int32
for k, v := range c.index.shapes {
if v == shape {
shapeID = k
}
}
for _, cell := range c.cells {
if cell == nil {
}
clipped := cell.findByShapeID(shapeID)
if clipped == nil {
continue
}
for _, j := range clipped.edges {
edges = append(edges, j)
}
}
if len(c.cells) > 1 {
edges = uniqueInts(edges)
}
return edges
}
// uniqueInts returns the sorted uniqued values from the given input.
func uniqueInts(in []int) []int {
var edges []int
m := make(map[int]bool)
for _, i := range in {
if m[i] {
continue
}
m[i] = true
edges = append(edges, i)
}
sort.Ints(edges)
return edges
}
// candidatesEdgeMap returns a map from shapes to the superse of edges for that
// shape that intersect the edge AB.
//
// CAVEAT: This method may return shapes that have an empty set of candidate edges.
// However the return value is non-empty only if at least one shape has a candidate edge.
func (c *CrossingEdgeQuery) candidatesEdgeMap(a, b Point) EdgeMap {
edgeMap := make(EdgeMap, 0)
// If there are only a few edges then it's faster to use brute force. We
// only bother with this optimization when there is a single shape.
if len(c.index.shapes) == 1 {
// Typically this method is called many times, so it is worth checking
// whether the edge map is empty or already consists of a single entry for
// this shape, and skip clearing edge map in that case.
shape := c.index.Shape(0)
// Note that we leave the edge map non-empty even if there are no candidates
// (i.e., there is a single entry with an empty set of edges).
edgeMap[shape] = c.candidates(a, b, shape)
return edgeMap
}
// Compute the set of index cells intersected by the query edge.
c.getCellsForEdge(a, b)
if len(c.cells) == 0 {
return edgeMap
}
// Gather all the edges that intersect those cells and sort them.
for _, cell := range c.cells {
for _, clipped := range cell.shapes {
s := c.index.Shape(clipped.shapeID)
for j := 0; j < clipped.numEdges(); j++ {
edgeMap[s] = append(edgeMap[s], clipped.edges[j])
}
}
}
if len(c.cells) > 1 {
for s, edges := range edgeMap {
edgeMap[s] = uniqueInts(edges)
}
}
return edgeMap
}
// getCells returns the set of ShapeIndexCells that might contain edges intersecting
// the edge AB in the given cell root. This method is used primarly by loop and shapeutil.
func (c *CrossingEdgeQuery) getCells(a, b Point, root *PaddedCell) []*ShapeIndexCell {
aUV, bUV, ok := ClipToFace(a, b, root.id.Face())
if ok {
c.a = aUV
c.b = bUV
edgeBound := r2.RectFromPoints(c.a, c.b)
if root.Bound().Intersects(edgeBound) {
c.computeCellsIntersected(root, edgeBound)
}
}
if len(c.cells) == 0 {
return nil
}
return c.cells
}
// getCellsForEdge populates the cells field to the set of index cells intersected by an edge AB.
func (c *CrossingEdgeQuery) getCellsForEdge(a, b Point) {
c.cells = nil
segments := FaceSegments(a, b)
for _, segment := range segments {
c.a = segment.a
c.b = segment.b
// Optimization: rather than always starting the recursive subdivision at
// the top level face cell, instead we start at the smallest S2CellId that
// contains the edge (the edge root cell). This typically lets us skip
// quite a few levels of recursion since most edges are short.
edgeBound := r2.RectFromPoints(c.a, c.b)
pcell := PaddedCellFromCellID(CellIDFromFace(segment.face), 0)
edgeRoot := pcell.ShrinkToFit(edgeBound)
// Now we need to determine how the edge root cell is related to the cells
// in the spatial index (cellMap). There are three cases:
//
// 1. edgeRoot is an index cell or is contained within an index cell.
// In this case we only need to look at the contents of that cell.
// 2. edgeRoot is subdivided into one or more index cells. In this case
// we recursively subdivide to find the cells intersected by AB.
// 3. edgeRoot does not intersect any index cells. In this case there
// is nothing to do.
relation := c.iter.LocateCellID(edgeRoot)
if relation == Indexed {
// edgeRoot is an index cell or is contained by an index cell (case 1).
c.cells = append(c.cells, c.iter.IndexCell())
} else if relation == Subdivided {
// edgeRoot is subdivided into one or more index cells (case 2). We
// find the cells intersected by AB using recursive subdivision.
if !edgeRoot.isFace() {
pcell = PaddedCellFromCellID(edgeRoot, 0)
}
c.computeCellsIntersected(pcell, edgeBound)
}
}
}
// computeCellsIntersected computes the index cells intersected by the current
// edge that are descendants of pcell and adds them to this queries set of cells.
func (c *CrossingEdgeQuery) computeCellsIntersected(pcell *PaddedCell, edgeBound r2.Rect) {
c.iter.seek(pcell.id.RangeMin())
if c.iter.Done() || c.iter.CellID() > pcell.id.RangeMax() {
// The index does not contain pcell or any of its descendants.
return
}
if c.iter.CellID() == pcell.id {
// The index contains this cell exactly.
c.cells = append(c.cells, c.iter.IndexCell())
return
}
// Otherwise, split the edge among the four children of pcell.
center := pcell.Middle().Lo()
if edgeBound.X.Hi < center.X {
// Edge is entirely contained in the two left children.
c.clipVAxis(edgeBound, center.Y, 0, pcell)
return
} else if edgeBound.X.Lo >= center.X {
// Edge is entirely contained in the two right children.
c.clipVAxis(edgeBound, center.Y, 1, pcell)
return
}
childBounds := c.splitUBound(edgeBound, center.X)
if edgeBound.Y.Hi < center.Y {
// Edge is entirely contained in the two lower children.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 0, 0), childBounds[0])
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 1, 0), childBounds[1])
} else if edgeBound.Y.Lo >= center.Y {
// Edge is entirely contained in the two upper children.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 0, 1), childBounds[0])
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, 1, 1), childBounds[1])
} else {
// The edge bound spans all four children. The edge itself intersects
// at most three children (since no padding is being used).
c.clipVAxis(childBounds[0], center.Y, 0, pcell)
c.clipVAxis(childBounds[1], center.Y, 1, pcell)
}
}
// clipVAxis computes the intersected cells recursively for a given padded cell.
// Given either the left (i=0) or right (i=1) side of a padded cell pcell,
// determine whether the current edge intersects the lower child, upper child,
// or both children, and call c.computeCellsIntersected recursively on those children.
// The center is the v-coordinate at the center of pcell.
func (c *CrossingEdgeQuery) clipVAxis(edgeBound r2.Rect, center float64, i int, pcell *PaddedCell) {
if edgeBound.Y.Hi < center {
// Edge is entirely contained in the lower child.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 0), edgeBound)
} else if edgeBound.Y.Lo >= center {
// Edge is entirely contained in the upper child.
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 1), edgeBound)
} else {
// The edge intersects both children.
childBounds := c.splitVBound(edgeBound, center)
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 0), childBounds[0])
c.computeCellsIntersected(PaddedCellFromParentIJ(pcell, i, 1), childBounds[1])
}
}
// splitUBound returns the bound for two children as a result of spliting the
// current edge at the given value U.
func (c *CrossingEdgeQuery) splitUBound(edgeBound r2.Rect, u float64) [2]r2.Rect {
v := edgeBound.Y.ClampPoint(interpolateFloat64(u, c.a.X, c.b.X, c.a.Y, c.b.Y))
// diag indicates which diagonal of the bounding box is spanned by AB:
// it is 0 if AB has positive slope, and 1 if AB has negative slope.
var diag int
if (c.a.X > c.b.X) != (c.a.Y > c.b.Y) {
diag = 1
}
return splitBound(edgeBound, 0, diag, u, v)
}
// splitVBound returns the bound for two children as a result of spliting the
// current edge into two child edges at the given value V.
func (c *CrossingEdgeQuery) splitVBound(edgeBound r2.Rect, v float64) [2]r2.Rect {
u := edgeBound.X.ClampPoint(interpolateFloat64(v, c.a.Y, c.b.Y, c.a.X, c.b.X))
var diag int
if (c.a.X > c.b.X) != (c.a.Y > c.b.Y) {
diag = 1
}
return splitBound(edgeBound, diag, 0, u, v)
}
// splitBound returns the bounds for the two childrenn as a result of spliting
// the current edge into two child edges at the given point (u,v). uEnd and vEnd
// indicate which bound endpoints of the first child will be updated.
func splitBound(edgeBound r2.Rect, uEnd, vEnd int, u, v float64) [2]r2.Rect {
var childBounds = [2]r2.Rect{
edgeBound,
edgeBound,
}
if uEnd == 1 {
childBounds[0].X.Lo = u
childBounds[1].X.Hi = u
} else {
childBounds[0].X.Hi = u
childBounds[1].X.Lo = u
}
if vEnd == 1 {
childBounds[0].Y.Lo = v
childBounds[1].Y.Hi = v
} else {
childBounds[0].Y.Hi = v
childBounds[1].Y.Lo = v
}
return childBounds
}

View file

@ -1,29 +0,0 @@
// Copyright 2014 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 implements types and functions for working with geometry in S² (spherical geometry).
Its related packages, parallel to this one, are s1 (operates on S¹), r1 (operates on ¹)
and r3 (operates on ³).
This package provides types and functions for the S2 cell hierarchy and coordinate systems.
The S2 cell hierarchy is a hierarchical decomposition of the surface of a unit sphere (S²)
into ``cells''; it is highly efficient, scales from continental size to under 1 cm²
and preserves spatial locality (nearby cells have close IDs).
A presentation that gives an overview of S2 is
https://docs.google.com/presentation/d/1Hl4KapfAENAOf4gv-pSngKwvS_jwNVHRPZTTDzXXn6Q/view.
*/
package s2

View file

@ -1,672 +0,0 @@
// Copyright 2017 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 contains a collection of methods for:
//
// (1) Robustly clipping geodesic edges to the faces of the S2 biunit cube
// (see s2stuv), and
//
// (2) Robustly clipping 2D edges against 2D rectangles.
//
// These functions can be used to efficiently find the set of CellIDs that
// are intersected by a geodesic edge (e.g., see CrossingEdgeQuery).
import (
"math"
"github.com/golang/geo/r1"
"github.com/golang/geo/r2"
"github.com/golang/geo/r3"
)
const (
// edgeClipErrorUVCoord is the maximum error in a u- or v-coordinate
// compared to the exact result, assuming that the points A and B are in
// the rectangle [-1,1]x[1,1] or slightly outside it (by 1e-10 or less).
edgeClipErrorUVCoord = 2.25 * dblEpsilon
// edgeClipErrorUVDist is the maximum distance from a clipped point to
// the corresponding exact result. It is equal to the error in a single
// coordinate because at most one coordinate is subject to error.
edgeClipErrorUVDist = 2.25 * dblEpsilon
// faceClipErrorRadians is the maximum angle between a returned vertex
// and the nearest point on the exact edge AB. It is equal to the
// maximum directional error in PointCross, plus the error when
// projecting points onto a cube face.
faceClipErrorRadians = 3 * dblEpsilon
// faceClipErrorDist is the same angle expressed as a maximum distance
// in (u,v)-space. In other words, a returned vertex is at most this far
// from the exact edge AB projected into (u,v)-space.
faceClipErrorUVDist = 9 * dblEpsilon
// faceClipErrorUVCoord is the maximum angle between a returned vertex
// and the nearest point on the exact edge AB expressed as the maximum error
// in an individual u- or v-coordinate. In other words, for each
// returned vertex there is a point on the exact edge AB whose u- and
// v-coordinates differ from the vertex by at most this amount.
faceClipErrorUVCoord = 9.0 * (1.0 / math.Sqrt2) * dblEpsilon
// intersectsRectErrorUVDist is the maximum error when computing if a point
// intersects with a given Rect. If some point of AB is inside the
// rectangle by at least this distance, the result is guaranteed to be true;
// if all points of AB are outside the rectangle by at least this distance,
// the result is guaranteed to be false. This bound assumes that rect is
// a subset of the rectangle [-1,1]x[-1,1] or extends slightly outside it
// (e.g., by 1e-10 or less).
intersectsRectErrorUVDist = 3 * math.Sqrt2 * dblEpsilon
)
// ClipToFace returns the (u,v) coordinates for the portion of the edge AB that
// intersects the given face, or false if the edge AB does not intersect.
// This method guarantees that the clipped vertices lie within the [-1,1]x[-1,1]
// cube face rectangle and are within faceClipErrorUVDist of the line AB, but
// the results may differ from those produced by FaceSegments.
func ClipToFace(a, b Point, face int) (aUV, bUV r2.Point, intersects bool) {
return ClipToPaddedFace(a, b, face, 0.0)
}
// ClipToPaddedFace returns the (u,v) coordinates for the portion of the edge AB that
// intersects the given face, but rather than clipping to the square [-1,1]x[-1,1]
// in (u,v) space, this method clips to [-R,R]x[-R,R] where R=(1+padding).
// Padding must be non-negative.
func ClipToPaddedFace(a, b Point, f int, padding float64) (aUV, bUV r2.Point, intersects bool) {
// Fast path: both endpoints are on the given face.
if face(a.Vector) == f && face(b.Vector) == f {
au, av := validFaceXYZToUV(f, a.Vector)
bu, bv := validFaceXYZToUV(f, b.Vector)
return r2.Point{au, av}, r2.Point{bu, bv}, true
}
// Convert everything into the (u,v,w) coordinates of the given face. Note
// that the cross product *must* be computed in the original (x,y,z)
// coordinate system because PointCross (unlike the mathematical cross
// product) can produce different results in different coordinate systems
// when one argument is a linear multiple of the other, due to the use of
// symbolic perturbations.
normUVW := pointUVW(faceXYZtoUVW(f, a.PointCross(b)))
aUVW := pointUVW(faceXYZtoUVW(f, a))
bUVW := pointUVW(faceXYZtoUVW(f, b))
// Padding is handled by scaling the u- and v-components of the normal.
// Letting R=1+padding, this means that when we compute the dot product of
// the normal with a cube face vertex (such as (-1,-1,1)), we will actually
// compute the dot product with the scaled vertex (-R,-R,1). This allows
// methods such as intersectsFace, exitAxis, etc, to handle padding
// with no further modifications.
scaleUV := 1 + padding
scaledN := pointUVW{r3.Vector{X: scaleUV * normUVW.X, Y: scaleUV * normUVW.Y, Z: normUVW.Z}}
if !scaledN.intersectsFace() {
return aUV, bUV, false
}
// TODO(roberts): This is a workaround for extremely small vectors where some
// loss of precision can occur in Normalize causing underflow. When PointCross
// is updated to work around this, this can be removed.
if math.Max(math.Abs(normUVW.X), math.Max(math.Abs(normUVW.Y), math.Abs(normUVW.Z))) < math.Ldexp(1, -511) {
normUVW = pointUVW{normUVW.Mul(math.Ldexp(1, 563))}
}
normUVW = pointUVW{normUVW.Normalize()}
aTan := pointUVW{normUVW.Cross(aUVW.Vector)}
bTan := pointUVW{bUVW.Cross(normUVW.Vector)}
// As described in clipDestination, if the sum of the scores from clipping the two
// endpoints is 3 or more, then the segment does not intersect this face.
aUV, aScore := clipDestination(bUVW, aUVW, pointUVW{scaledN.Mul(-1)}, bTan, aTan, scaleUV)
bUV, bScore := clipDestination(aUVW, bUVW, scaledN, aTan, bTan, scaleUV)
return aUV, bUV, aScore+bScore < 3
}
// ClipEdge returns the portion of the edge defined by AB that is contained by the
// given rectangle. If there is no intersection, false is returned and aClip and bClip
// are undefined.
func ClipEdge(a, b r2.Point, clip r2.Rect) (aClip, bClip r2.Point, intersects bool) {
// Compute the bounding rectangle of AB, clip it, and then extract the new
// endpoints from the clipped bound.
bound := r2.RectFromPoints(a, b)
if bound, intersects = clipEdgeBound(a, b, clip, bound); !intersects {
return aClip, bClip, false
}
ai := 0
if a.X > b.X {
ai = 1
}
aj := 0
if a.Y > b.Y {
aj = 1
}
return bound.VertexIJ(ai, aj), bound.VertexIJ(1-ai, 1-aj), true
}
// The three functions below (sumEqual, intersectsFace, intersectsOppositeEdges)
// all compare a sum (u + v) to a third value w. They are implemented in such a
// way that they produce an exact result even though all calculations are done
// with ordinary floating-point operations. Here are the principles on which these
// functions are based:
//
// A. If u + v < w in floating-point, then u + v < w in exact arithmetic.
//
// B. If u + v < w in exact arithmetic, then at least one of the following
// expressions is true in floating-point:
// u + v < w
// u < w - v
// v < w - u
//
// Proof: By rearranging terms and substituting ">" for "<", we can assume
// that all values are non-negative. Now clearly "w" is not the smallest
// value, so assume WLOG that "u" is the smallest. We want to show that
// u < w - v in floating-point. If v >= w/2, the calculation of w - v is
// exact since the result is smaller in magnitude than either input value,
// so the result holds. Otherwise we have u <= v < w/2 and w - v >= w/2
// (even in floating point), so the result also holds.
// sumEqual reports whether u + v == w exactly.
func sumEqual(u, v, w float64) bool {
return (u+v == w) && (u == w-v) && (v == w-u)
}
// pointUVW represents a Point in (u,v,w) coordinate space of a cube face.
type pointUVW Point
// intersectsFace reports whether a given directed line L intersects the cube face F.
// The line L is defined by its normal N in the (u,v,w) coordinates of F.
func (p pointUVW) intersectsFace() bool {
// L intersects the [-1,1]x[-1,1] square in (u,v) if and only if the dot
// products of N with the four corner vertices (-1,-1,1), (1,-1,1), (1,1,1),
// and (-1,1,1) do not all have the same sign. This is true exactly when
// |Nu| + |Nv| >= |Nw|. The code below evaluates this expression exactly.
u := math.Abs(p.X)
v := math.Abs(p.Y)
w := math.Abs(p.Z)
// We only need to consider the cases where u or v is the smallest value,
// since if w is the smallest then both expressions below will have a
// positive LHS and a negative RHS.
return (v >= w-u) && (u >= w-v)
}
// intersectsOppositeEdges reports whether a directed line L intersects two
// opposite edges of a cube face F. This includs the case where L passes
// exactly through a corner vertex of F. The directed line L is defined
// by its normal N in the (u,v,w) coordinates of F.
func (p pointUVW) intersectsOppositeEdges() bool {
// The line L intersects opposite edges of the [-1,1]x[-1,1] (u,v) square if
// and only exactly two of the corner vertices lie on each side of L. This
// is true exactly when ||Nu| - |Nv|| >= |Nw|. The code below evaluates this
// expression exactly.
u := math.Abs(p.X)
v := math.Abs(p.Y)
w := math.Abs(p.Z)
// If w is the smallest, the following line returns an exact result.
if math.Abs(u-v) != w {
return math.Abs(u-v) >= w
}
// Otherwise u - v = w exactly, or w is not the smallest value. In either
// case the following returns the correct result.
if u >= v {
return u-w >= v
}
return v-w >= u
}
// axis represents the possible results of exitAxis.
type axis int
const (
axisU axis = iota
axisV
)
// exitAxis reports which axis the directed line L exits the cube face F on.
// The directed line L is represented by its CCW normal N in the (u,v,w) coordinates
// of F. It returns axisU if L exits through the u=-1 or u=+1 edge, and axisV if L exits
// through the v=-1 or v=+1 edge. Either result is acceptable if L exits exactly
// through a corner vertex of the cube face.
func (p pointUVW) exitAxis() axis {
if p.intersectsOppositeEdges() {
// The line passes through through opposite edges of the face.
// It exits through the v=+1 or v=-1 edge if the u-component of N has a
// larger absolute magnitude than the v-component.
if math.Abs(p.X) >= math.Abs(p.Y) {
return axisV
}
return axisU
}
// The line passes through through two adjacent edges of the face.
// It exits the v=+1 or v=-1 edge if an even number of the components of N
// are negative. We test this using signbit() rather than multiplication
// to avoid the possibility of underflow.
var x, y, z int
if math.Signbit(p.X) {
x = 1
}
if math.Signbit(p.Y) {
y = 1
}
if math.Signbit(p.Z) {
z = 1
}
if x^y^z == 0 {
return axisV
}
return axisU
}
// exitPoint returns the UV coordinates of the point where a directed line L (represented
// by the CCW normal of this point), exits the cube face this point is derived from along
// the given axis.
func (p pointUVW) exitPoint(a axis) r2.Point {
if a == axisU {
u := -1.0
if p.Y > 0 {
u = 1.0
}
return r2.Point{u, (-u*p.X - p.Z) / p.Y}
}
v := -1.0
if p.X < 0 {
v = 1.0
}
return r2.Point{(-v*p.Y - p.Z) / p.X, v}
}
// clipDestination returns a score which is used to indicate if the clipped edge AB
// on the given face intersects the face at all. This function returns the score for
// the given endpoint, which is an integer ranging from 0 to 3. If the sum of the scores
// from both of the endpoints is 3 or more, then edge AB does not intersect this face.
//
// First, it clips the line segment AB to find the clipped destination B' on a given
// face. (The face is specified implicitly by expressing *all arguments* in the (u,v,w)
// coordinates of that face.) Second, it partially computes whether the segment AB
// intersects this face at all. The actual condition is fairly complicated, but it
// turns out that it can be expressed as a "score" that can be computed independently
// when clipping the two endpoints A and B.
func clipDestination(a, b, scaledN, aTan, bTan pointUVW, scaleUV float64) (r2.Point, int) {
var uv r2.Point
// Optimization: if B is within the safe region of the face, use it.
maxSafeUVCoord := 1 - faceClipErrorUVCoord
if b.Z > 0 {
uv = r2.Point{b.X / b.Z, b.Y / b.Z}
if math.Max(math.Abs(uv.X), math.Abs(uv.Y)) <= maxSafeUVCoord {
return uv, 0
}
}
// Otherwise find the point B' where the line AB exits the face.
uv = scaledN.exitPoint(scaledN.exitAxis()).Mul(scaleUV)
p := pointUVW(Point{r3.Vector{uv.X, uv.Y, 1.0}})
// Determine if the exit point B' is contained within the segment. We do this
// by computing the dot products with two inward-facing tangent vectors at A
// and B. If either dot product is negative, we say that B' is on the "wrong
// side" of that point. As the point B' moves around the great circle AB past
// the segment endpoint B, it is initially on the wrong side of B only; as it
// moves further it is on the wrong side of both endpoints; and then it is on
// the wrong side of A only. If the exit point B' is on the wrong side of
// either endpoint, we can't use it; instead the segment is clipped at the
// original endpoint B.
//
// We reject the segment if the sum of the scores of the two endpoints is 3
// or more. Here is what that rule encodes:
// - If B' is on the wrong side of A, then the other clipped endpoint A'
// must be in the interior of AB (otherwise AB' would go the wrong way
// around the circle). There is a similar rule for A'.
// - If B' is on the wrong side of either endpoint (and therefore we must
// use the original endpoint B instead), then it must be possible to
// project B onto this face (i.e., its w-coordinate must be positive).
// This rule is only necessary to handle certain zero-length edges (A=B).
score := 0
if p.Sub(a.Vector).Dot(aTan.Vector) < 0 {
score = 2 // B' is on wrong side of A.
} else if p.Sub(b.Vector).Dot(bTan.Vector) < 0 {
score = 1 // B' is on wrong side of B.
}
if score > 0 { // B' is not in the interior of AB.
if b.Z <= 0 {
score = 3 // B cannot be projected onto this face.
} else {
uv = r2.Point{b.X / b.Z, b.Y / b.Z}
}
}
return uv, score
}
// updateEndpoint returns the interval with the specified endpoint updated to
// the given value. If the value lies beyond the opposite endpoint, nothing is
// changed and false is returned.
func updateEndpoint(bound r1.Interval, highEndpoint bool, value float64) (r1.Interval, bool) {
if !highEndpoint {
if bound.Hi < value {
return bound, false
}
if bound.Lo < value {
bound.Lo = value
}
return bound, true
}
if bound.Lo > value {
return bound, false
}
if bound.Hi > value {
bound.Hi = value
}
return bound, true
}
// clipBoundAxis returns the clipped versions of the bounding intervals for the given
// axes for the line segment from (a0,a1) to (b0,b1) so that neither extends beyond the
// given clip interval. negSlope is a precomputed helper variable that indicates which
// diagonal of the bounding box is spanned by AB; it is false if AB has positive slope,
// and true if AB has negative slope. If the clipping interval doesn't overlap the bounds,
// false is returned.
func clipBoundAxis(a0, b0 float64, bound0 r1.Interval, a1, b1 float64, bound1 r1.Interval,
negSlope bool, clip r1.Interval) (bound0c, bound1c r1.Interval, updated bool) {
if bound0.Lo < clip.Lo {
// If the upper bound is below the clips lower bound, there is nothing to do.
if bound0.Hi < clip.Lo {
return bound0, bound1, false
}
// narrow the intervals lower bound to the clip bound.
bound0.Lo = clip.Lo
if bound1, updated = updateEndpoint(bound1, negSlope, interpolateFloat64(clip.Lo, a0, b0, a1, b1)); !updated {
return bound0, bound1, false
}
}
if bound0.Hi > clip.Hi {
// If the lower bound is above the clips upper bound, there is nothing to do.
if bound0.Lo > clip.Hi {
return bound0, bound1, false
}
// narrow the intervals upper bound to the clip bound.
bound0.Hi = clip.Hi
if bound1, updated = updateEndpoint(bound1, !negSlope, interpolateFloat64(clip.Hi, a0, b0, a1, b1)); !updated {
return bound0, bound1, false
}
}
return bound0, bound1, true
}
// edgeIntersectsRect reports whether the edge defined by AB intersects the
// given closed rectangle to within the error bound.
func edgeIntersectsRect(a, b r2.Point, r r2.Rect) bool {
// First check whether the bounds of a Rect around AB intersects the given rect.
if !r.Intersects(r2.RectFromPoints(a, b)) {
return false
}
// Otherwise AB intersects the rect if and only if all four vertices of rect
// do not lie on the same side of the extended line AB. We test this by finding
// the two vertices of rect with minimum and maximum projections onto the normal
// of AB, and computing their dot products with the edge normal.
n := b.Sub(a).Ortho()
i := 0
if n.X >= 0 {
i = 1
}
j := 0
if n.Y >= 0 {
j = 1
}
max := n.Dot(r.VertexIJ(i, j).Sub(a))
min := n.Dot(r.VertexIJ(1-i, 1-j).Sub(a))
return (max >= 0) && (min <= 0)
}
// clippedEdgeBound returns the bounding rectangle of the portion of the edge defined
// by AB intersected by clip. The resulting bound may be empty. This is a convenience
// function built on top of clipEdgeBound.
func clippedEdgeBound(a, b r2.Point, clip r2.Rect) r2.Rect {
bound := r2.RectFromPoints(a, b)
if b1, intersects := clipEdgeBound(a, b, clip, bound); intersects {
return b1
}
return r2.EmptyRect()
}
// clipEdgeBound clips an edge AB to sequence of rectangles efficiently.
// It represents the clipped edges by their bounding boxes rather than as a pair of
// endpoints. Specifically, let A'B' be some portion of an edge AB, and let bound be
// a tight bound of A'B'. This function returns the bound that is a tight bound
// of A'B' intersected with a given rectangle. If A'B' does not intersect clip,
// it returns false and the original bound.
func clipEdgeBound(a, b r2.Point, clip, bound r2.Rect) (r2.Rect, bool) {
// negSlope indicates which diagonal of the bounding box is spanned by AB: it
// is false if AB has positive slope, and true if AB has negative slope. This is
// used to determine which interval endpoints need to be updated each time
// the edge is clipped.
negSlope := (a.X > b.X) != (a.Y > b.Y)
b0x, b0y, up1 := clipBoundAxis(a.X, b.X, bound.X, a.Y, b.Y, bound.Y, negSlope, clip.X)
if !up1 {
return bound, false
}
b1y, b1x, up2 := clipBoundAxis(a.Y, b.Y, b0y, a.X, b.X, b0x, negSlope, clip.Y)
if !up2 {
return r2.Rect{b0x, b0y}, false
}
return r2.Rect{X: b1x, Y: b1y}, true
}
// interpolateFloat64 returns a value with the same combination of a1 and b1 as the
// given value x is of a and b. This function makes the following guarantees:
// - If x == a, then x1 = a1 (exactly).
// - If x == b, then x1 = b1 (exactly).
// - If a <= x <= b, then a1 <= x1 <= b1 (even if a1 == b1).
// This requires a != b.
func interpolateFloat64(x, a, b, a1, b1 float64) float64 {
// To get results that are accurate near both A and B, we interpolate
// starting from the closer of the two points.
if math.Abs(a-x) <= math.Abs(b-x) {
return a1 + (b1-a1)*(x-a)/(b-a)
}
return b1 + (a1-b1)*(x-b)/(a-b)
}
// FaceSegment represents an edge AB clipped to an S2 cube face. It is
// represented by a face index and a pair of (u,v) coordinates.
type FaceSegment struct {
face int
a, b r2.Point
}
// FaceSegments subdivides the given edge AB at every point where it crosses the
// boundary between two S2 cube faces and returns the corresponding FaceSegments.
// The segments are returned in order from A toward B. The input points must be
// unit length.
//
// This function guarantees that the returned segments form a continuous path
// from A to B, and that all vertices are within faceClipErrorUVDist of the
// line AB. All vertices lie within the [-1,1]x[-1,1] cube face rectangles.
// The results are consistent with Sign, i.e. the edge is well-defined even its
// endpoints are antipodal.
// TODO(roberts): Extend the implementation of PointCross so that this is true.
func FaceSegments(a, b Point) []FaceSegment {
var segment FaceSegment
// Fast path: both endpoints are on the same face.
var aFace, bFace int
aFace, segment.a.X, segment.a.Y = xyzToFaceUV(a.Vector)
bFace, segment.b.X, segment.b.Y = xyzToFaceUV(b.Vector)
if aFace == bFace {
segment.face = aFace
return []FaceSegment{segment}
}
// Starting at A, we follow AB from face to face until we reach the face
// containing B. The following code is designed to ensure that we always
// reach B, even in the presence of numerical errors.
//
// First we compute the normal to the plane containing A and B. This normal
// becomes the ultimate definition of the line AB; it is used to resolve all
// questions regarding where exactly the line goes. Unfortunately due to
// numerical errors, the line may not quite intersect the faces containing
// the original endpoints. We handle this by moving A and/or B slightly if
// necessary so that they are on faces intersected by the line AB.
ab := a.PointCross(b)
aFace, segment.a = moveOriginToValidFace(aFace, a, ab, segment.a)
bFace, segment.b = moveOriginToValidFace(bFace, b, Point{ab.Mul(-1)}, segment.b)
// Now we simply follow AB from face to face until we reach B.
var segments []FaceSegment
segment.face = aFace
bSaved := segment.b
for face := aFace; face != bFace; {
// Complete the current segment by finding the point where AB
// exits the current face.
z := faceXYZtoUVW(face, ab)
n := pointUVW{z.Vector}
exitAxis := n.exitAxis()
segment.b = n.exitPoint(exitAxis)
segments = append(segments, segment)
// Compute the next face intersected by AB, and translate the exit
// point of the current segment into the (u,v) coordinates of the
// next face. This becomes the first point of the next segment.
exitXyz := faceUVToXYZ(face, segment.b.X, segment.b.Y)
face = nextFace(face, segment.b, exitAxis, n, bFace)
exitUvw := faceXYZtoUVW(face, Point{exitXyz})
segment.face = face
segment.a = r2.Point{exitUvw.X, exitUvw.Y}
}
// Finish the last segment.
segment.b = bSaved
return append(segments, segment)
}
// moveOriginToValidFace updates the origin point to a valid face if necessary.
// Given a line segment AB whose origin A has been projected onto a given cube
// face, determine whether it is necessary to project A onto a different face
// instead. This can happen because the normal of the line AB is not computed
// exactly, so that the line AB (defined as the set of points perpendicular to
// the normal) may not intersect the cube face containing A. Even if it does
// intersect the face, the exit point of the line from that face may be on
// the wrong side of A (i.e., in the direction away from B). If this happens,
// we reproject A onto the adjacent face where the line AB approaches A most
// closely. This moves the origin by a small amount, but never more than the
// error tolerances.
func moveOriginToValidFace(face int, a, ab Point, aUV r2.Point) (int, r2.Point) {
// Fast path: if the origin is sufficiently far inside the face, it is
// always safe to use it.
const maxSafeUVCoord = 1 - faceClipErrorUVCoord
if math.Max(math.Abs((aUV).X), math.Abs((aUV).Y)) <= maxSafeUVCoord {
return face, aUV
}
// Otherwise check whether the normal AB even intersects this face.
z := faceXYZtoUVW(face, ab)
n := pointUVW{z.Vector}
if n.intersectsFace() {
// Check whether the point where the line AB exits this face is on the
// wrong side of A (by more than the acceptable error tolerance).
uv := n.exitPoint(n.exitAxis())
exit := faceUVToXYZ(face, uv.X, uv.Y)
aTangent := ab.Normalize().Cross(a.Vector)
// We can use the given face.
if exit.Sub(a.Vector).Dot(aTangent) >= -faceClipErrorRadians {
return face, aUV
}
}
// Otherwise we reproject A to the nearest adjacent face. (If line AB does
// not pass through a given face, it must pass through all adjacent faces.)
var dir int
if math.Abs((aUV).X) >= math.Abs((aUV).Y) {
// U-axis
if aUV.X > 0 {
dir = 1
}
face = uvwFace(face, 0, dir)
} else {
// V-axis
if aUV.Y > 0 {
dir = 1
}
face = uvwFace(face, 1, dir)
}
aUV.X, aUV.Y = validFaceXYZToUV(face, a.Vector)
aUV.X = math.Max(-1.0, math.Min(1.0, aUV.X))
aUV.Y = math.Max(-1.0, math.Min(1.0, aUV.Y))
return face, aUV
}
// nextFace returns the next face that should be visited by FaceSegments, given that
// we have just visited face and we are following the line AB (represented
// by its normal N in the (u,v,w) coordinates of that face). The other
// arguments include the point where AB exits face, the corresponding
// exit axis, and the target face containing the destination point B.
func nextFace(face int, exit r2.Point, axis axis, n pointUVW, targetFace int) int {
// this bit is to work around C++ cleverly casting bools to ints for you.
exitA := exit.X
exit1MinusA := exit.Y
if axis == axisV {
exitA = exit.Y
exit1MinusA = exit.X
}
exitAPos := 0
if exitA > 0 {
exitAPos = 1
}
exit1MinusAPos := 0
if exit1MinusA > 0 {
exit1MinusAPos = 1
}
// We return the face that is adjacent to the exit point along the given
// axis. If line AB exits *exactly* through a corner of the face, there are
// two possible next faces. If one is the target face containing B, then
// we guarantee that we advance to that face directly.
//
// The three conditions below check that (1) AB exits approximately through
// a corner, (2) the adjacent face along the non-exit axis is the target
// face, and (3) AB exits *exactly* through the corner. (The sumEqual
// code checks whether the dot product of (u,v,1) and n is exactly zero.)
if math.Abs(exit1MinusA) == 1 &&
uvwFace(face, int(1-axis), exit1MinusAPos) == targetFace &&
sumEqual(exit.X*n.X, exit.Y*n.Y, -n.Z) {
return targetFace
}
// Otherwise return the face that is adjacent to the exit point in the
// direction of the exit axis.
return uvwFace(face, int(axis), exitAPos)
}

View file

@ -1,227 +0,0 @@
// Copyright 2017 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"
)
// EdgeCrosser allows edges to be efficiently tested for intersection with a
// given fixed edge AB. It is especially efficient when testing for
// intersection with an edge chain connecting vertices v0, v1, v2, ...
//
// Example usage:
//
// func CountIntersections(a, b Point, edges []Edge) int {
// count := 0
// crosser := NewEdgeCrosser(a, b)
// for _, edge := range edges {
// if crosser.CrossingSign(&edge.First, &edge.Second) != DoNotCross {
// count++
// }
// }
// return count
// }
//
type EdgeCrosser struct {
a Point
b Point
aXb Point
// To reduce the number of calls to expensiveSign, we compute an
// outward-facing tangent at A and B if necessary. If the plane
// perpendicular to one of these tangents separates AB from CD (i.e., one
// edge on each side) then there is no intersection.
aTangent Point // Outward-facing tangent at A.
bTangent Point // Outward-facing tangent at B.
// The fields below are updated for each vertex in the chain.
c Point // Previous vertex in the vertex chain.
acb Direction // The orientation of triangle ACB.
}
// NewEdgeCrosser returns an EdgeCrosser with the fixed edge AB.
func NewEdgeCrosser(a, b Point) *EdgeCrosser {
norm := a.PointCross(b)
return &EdgeCrosser{
a: a,
b: b,
aXb: Point{a.Cross(b.Vector)},
aTangent: Point{a.Cross(norm.Vector)},
bTangent: Point{norm.Cross(b.Vector)},
}
}
// CrossingSign reports whether the edge AB intersects the edge CD. If any two
// vertices from different edges are the same, returns MaybeCross. If either edge
// is degenerate (A == B or C == D), returns either DoNotCross or MaybeCross.
//
// Properties of CrossingSign:
//
// (1) CrossingSign(b,a,c,d) == CrossingSign(a,b,c,d)
// (2) CrossingSign(c,d,a,b) == CrossingSign(a,b,c,d)
// (3) CrossingSign(a,b,c,d) == MaybeCross if a==c, a==d, b==c, b==d
// (3) CrossingSign(a,b,c,d) == DoNotCross or MaybeCross if a==b or c==d
//
// Note that if you want to check an edge against a chain of other edges,
// it is slightly more efficient to use the single-argument version
// ChainCrossingSign below.
func (e *EdgeCrosser) CrossingSign(c, d Point) Crossing {
if c != e.c {
e.RestartAt(c)
}
return e.ChainCrossingSign(d)
}
// EdgeOrVertexCrossing reports whether if CrossingSign(c, d) > 0, or AB and
// CD share a vertex and VertexCrossing(a, b, c, d) is true.
//
// This method extends the concept of a "crossing" to the case where AB
// and CD have a vertex in common. The two edges may or may not cross,
// according to the rules defined in VertexCrossing above. The rules
// are designed so that point containment tests can be implemented simply
// by counting edge crossings. Similarly, determining whether one edge
// chain crosses another edge chain can be implemented by counting.
func (e *EdgeCrosser) EdgeOrVertexCrossing(c, d Point) bool {
if c != e.c {
e.RestartAt(c)
}
return e.EdgeOrVertexChainCrossing(d)
}
// NewChainEdgeCrosser is a convenience constructor that uses AB as the fixed edge,
// and C as the first vertex of the vertex chain (equivalent to calling RestartAt(c)).
//
// You don't need to use this or any of the chain functions unless you're trying to
// squeeze out every last drop of performance. Essentially all you are saving is a test
// whether the first vertex of the current edge is the same as the second vertex of the
// previous edge.
func NewChainEdgeCrosser(a, b, c Point) *EdgeCrosser {
e := NewEdgeCrosser(a, b)
e.RestartAt(c)
return e
}
// RestartAt sets the current point of the edge crosser to be c.
// Call this method when your chain 'jumps' to a new place.
// The argument must point to a value that persists until the next call.
func (e *EdgeCrosser) RestartAt(c Point) {
e.c = c
e.acb = -triageSign(e.a, e.b, e.c)
}
// ChainCrossingSign is like CrossingSign, but uses the last vertex passed to one of
// the crossing methods (or RestartAt) as the first vertex of the current edge.
func (e *EdgeCrosser) ChainCrossingSign(d Point) Crossing {
// For there to be an edge crossing, the triangles ACB, CBD, BDA, DAC must
// all be oriented the same way (CW or CCW). We keep the orientation of ACB
// as part of our state. When each new point D arrives, we compute the
// orientation of BDA and check whether it matches ACB. This checks whether
// the points C and D are on opposite sides of the great circle through AB.
// Recall that triageSign is invariant with respect to rotating its
// arguments, i.e. ABD has the same orientation as BDA.
bda := triageSign(e.a, e.b, d)
if e.acb == -bda && bda != Indeterminate {
// The most common case -- triangles have opposite orientations. Save the
// current vertex D as the next vertex C, and also save the orientation of
// the new triangle ACB (which is opposite to the current triangle BDA).
e.c = d
e.acb = -bda
return DoNotCross
}
return e.crossingSign(d, bda)
}
// EdgeOrVertexChainCrossing is like EdgeOrVertexCrossing, but uses the last vertex
// passed to one of the crossing methods (or RestartAt) as the first vertex of the current edge.
func (e *EdgeCrosser) EdgeOrVertexChainCrossing(d Point) bool {
// We need to copy e.c since it is clobbered by ChainCrossingSign.
c := e.c
switch e.ChainCrossingSign(d) {
case DoNotCross:
return false
case Cross:
return true
}
return VertexCrossing(e.a, e.b, c, d)
}
// crossingSign handle the slow path of CrossingSign.
func (e *EdgeCrosser) crossingSign(d Point, bda Direction) Crossing {
// Compute the actual result, and then save the current vertex D as the next
// vertex C, and save the orientation of the next triangle ACB (which is
// opposite to the current triangle BDA).
defer func() {
e.c = d
e.acb = -bda
}()
// At this point, a very common situation is that A,B,C,D are four points on
// a line such that AB does not overlap CD. (For example, this happens when
// a line or curve is sampled finely, or when geometry is constructed by
// computing the union of S2CellIds.) Most of the time, we can determine
// that AB and CD do not intersect using the two outward-facing
// tangents at A and B (parallel to AB) and testing whether AB and CD are on
// opposite sides of the plane perpendicular to one of these tangents. This
// is moderately expensive but still much cheaper than expensiveSign.
// The error in RobustCrossProd is insignificant. The maximum error in
// the call to CrossProd (i.e., the maximum norm of the error vector) is
// (0.5 + 1/sqrt(3)) * dblEpsilon. The maximum error in each call to
// DotProd below is dblEpsilon. (There is also a small relative error
// term that is insignificant because we are comparing the result against a
// constant that is very close to zero.)
maxError := (1.5 + 1/math.Sqrt(3)) * dblEpsilon
if (e.c.Dot(e.aTangent.Vector) > maxError && d.Dot(e.aTangent.Vector) > maxError) || (e.c.Dot(e.bTangent.Vector) > maxError && d.Dot(e.bTangent.Vector) > maxError) {
return DoNotCross
}
// Otherwise, eliminate the cases where two vertices from different edges are
// equal. (These cases could be handled in the code below, but we would rather
// avoid calling ExpensiveSign if possible.)
if e.a == e.c || e.a == d || e.b == e.c || e.b == d {
return MaybeCross
}
// Eliminate the cases where an input edge is degenerate. (Note that in
// most cases, if CD is degenerate then this method is not even called
// because acb and bda have different signs.)
if e.a == e.b || e.c == d {
return DoNotCross
}
// Otherwise it's time to break out the big guns.
if e.acb == Indeterminate {
e.acb = -expensiveSign(e.a, e.b, e.c)
}
if bda == Indeterminate {
bda = expensiveSign(e.a, e.b, d)
}
if bda != e.acb {
return DoNotCross
}
cbd := -RobustSign(e.c, d, e.b)
if cbd != e.acb {
return DoNotCross
}
dac := RobustSign(e.c, d, e.a)
if dac != e.acb {
return DoNotCross
}
return Cross
}

View file

@ -1,394 +0,0 @@
// Copyright 2017 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 (
"fmt"
"math"
"github.com/golang/geo/r3"
"github.com/golang/geo/s1"
)
const (
// intersectionError can be set somewhat arbitrarily, because the algorithm
// uses more precision if necessary in order to achieve the specified error.
// The only strict requirement is that intersectionError >= dblEpsilon
// radians. However, using a larger error tolerance makes the algorithm more
// efficient because it reduces the number of cases where exact arithmetic is
// needed.
intersectionError = s1.Angle(8 * dblEpsilon)
// intersectionMergeRadius is used to ensure that intersection points that
// are supposed to be coincident are merged back together into a single
// vertex. This is required in order for various polygon operations (union,
// intersection, etc) to work correctly. It is twice the intersection error
// because two coincident intersection points might have errors in
// opposite directions.
intersectionMergeRadius = 2 * intersectionError
)
// A Crossing indicates how edges cross.
type Crossing int
const (
// Cross means the edges cross.
Cross Crossing = iota
// MaybeCross means two vertices from different edges are the same.
MaybeCross
// DoNotCross means the edges do not cross.
DoNotCross
)
func (c Crossing) String() string {
switch c {
case Cross:
return "Cross"
case MaybeCross:
return "MaybeCross"
case DoNotCross:
return "DoNotCross"
default:
return fmt.Sprintf("(BAD CROSSING %d)", c)
}
}
// CrossingSign reports whether the edge AB intersects the edge CD.
// If AB crosses CD at a point that is interior to both edges, Cross is returned.
// If any two vertices from different edges are the same it returns MaybeCross.
// Otherwise it returns DoNotCross.
// If either edge is degenerate (A == B or C == D), the return value is MaybeCross
// if two vertices from different edges are the same and DoNotCross otherwise.
//
// Properties of CrossingSign:
//
// (1) CrossingSign(b,a,c,d) == CrossingSign(a,b,c,d)
// (2) CrossingSign(c,d,a,b) == CrossingSign(a,b,c,d)
// (3) CrossingSign(a,b,c,d) == MaybeCross if a==c, a==d, b==c, b==d
// (3) CrossingSign(a,b,c,d) == DoNotCross or MaybeCross if a==b or c==d
//
// This method implements an exact, consistent perturbation model such
// that no three points are ever considered to be collinear. This means
// that even if you have 4 points A, B, C, D that lie exactly in a line
// (say, around the equator), C and D will be treated as being slightly to
// one side or the other of AB. This is done in a way such that the
// results are always consistent (see RobustSign).
func CrossingSign(a, b, c, d Point) Crossing {
crosser := NewChainEdgeCrosser(a, b, c)
return crosser.ChainCrossingSign(d)
}
// VertexCrossing reports whether two edges "cross" in such a way that point-in-polygon
// containment tests can be implemented by counting the number of edge crossings.
//
// Given two edges AB and CD where at least two vertices are identical
// (i.e. CrossingSign(a,b,c,d) == 0), the basic rule is that a "crossing"
// occurs if AB is encountered after CD during a CCW sweep around the shared
// vertex starting from a fixed reference point.
//
// Note that according to this rule, if AB crosses CD then in general CD
// does not cross AB. However, this leads to the correct result when
// counting polygon edge crossings. For example, suppose that A,B,C are
// three consecutive vertices of a CCW polygon. If we now consider the edge
// crossings of a segment BP as P sweeps around B, the crossing number
// changes parity exactly when BP crosses BA or BC.
//
// Useful properties of VertexCrossing (VC):
//
// (1) VC(a,a,c,d) == VC(a,b,c,c) == false
// (2) VC(a,b,a,b) == VC(a,b,b,a) == true
// (3) VC(a,b,c,d) == VC(a,b,d,c) == VC(b,a,c,d) == VC(b,a,d,c)
// (3) If exactly one of a,b equals one of c,d, then exactly one of
// VC(a,b,c,d) and VC(c,d,a,b) is true
//
// It is an error to call this method with 4 distinct vertices.
func VertexCrossing(a, b, c, d Point) bool {
// If A == B or C == D there is no intersection. We need to check this
// case first in case 3 or more input points are identical.
if a == b || c == d {
return false
}
// If any other pair of vertices is equal, there is a crossing if and only
// if OrderedCCW indicates that the edge AB is further CCW around the
// shared vertex O (either A or B) than the edge CD, starting from an
// arbitrary fixed reference point.
switch {
case a == d:
return OrderedCCW(Point{a.Ortho()}, c, b, a)
case b == c:
return OrderedCCW(Point{b.Ortho()}, d, a, b)
case a == c:
return OrderedCCW(Point{a.Ortho()}, d, b, a)
case b == d:
return OrderedCCW(Point{b.Ortho()}, c, a, b)
}
return false
}
// EdgeOrVertexCrossing is a convenience function that calls CrossingSign to
// handle cases where all four vertices are distinct, and VertexCrossing to
// handle cases where two or more vertices are the same. This defines a crossing
// function such that point-in-polygon containment tests can be implemented
// by simply counting edge crossings.
func EdgeOrVertexCrossing(a, b, c, d Point) bool {
switch CrossingSign(a, b, c, d) {
case DoNotCross:
return false
case Cross:
return true
default:
return VertexCrossing(a, b, c, d)
}
}
// Intersection returns the intersection point of two edges AB and CD that cross
// (CrossingSign(a,b,c,d) == Crossing).
//
// Useful properties of Intersection:
//
// (1) Intersection(b,a,c,d) == Intersection(a,b,d,c) == Intersection(a,b,c,d)
// (2) Intersection(c,d,a,b) == Intersection(a,b,c,d)
//
// The returned intersection point X is guaranteed to be very close to the
// true intersection point of AB and CD, even if the edges intersect at a
// very small angle.
func Intersection(a0, a1, b0, b1 Point) Point {
// It is difficult to compute the intersection point of two edges accurately
// when the angle between the edges is very small. Previously we handled
// this by only guaranteeing that the returned intersection point is within
// intersectionError of each edge. However, this means that when the edges
// cross at a very small angle, the computed result may be very far from the
// true intersection point.
//
// Instead this function now guarantees that the result is always within
// intersectionError of the true intersection. This requires using more
// sophisticated techniques and in some cases extended precision.
//
// - intersectionStable computes the intersection point using
// projection and interpolation, taking care to minimize cancellation
// error.
//
// - intersectionExact computes the intersection point using precision
// arithmetic and converts the final result back to an Point.
pt, ok := intersectionStable(a0, a1, b0, b1)
if !ok {
pt = intersectionExact(a0, a1, b0, b1)
}
// Make sure the intersection point is on the correct side of the sphere.
// Since all vertices are unit length, and edges are less than 180 degrees,
// (a0 + a1) and (b0 + b1) both have positive dot product with the
// intersection point. We use the sum of all vertices to make sure that the
// result is unchanged when the edges are swapped or reversed.
if pt.Dot((a0.Add(a1.Vector)).Add(b0.Add(b1.Vector))) < 0 {
pt = Point{pt.Mul(-1)}
}
return pt
}
// Computes the cross product of two vectors, normalized to be unit length.
// Also returns the length of the cross
// product before normalization, which is useful for estimating the amount of
// error in the result. For numerical stability, the vectors should both be
// approximately unit length.
func robustNormalWithLength(x, y r3.Vector) (r3.Vector, float64) {
var pt r3.Vector
// This computes 2 * (x.Cross(y)), but has much better numerical
// stability when x and y are unit length.
tmp := x.Sub(y).Cross(x.Add(y))
length := tmp.Norm()
if length != 0 {
pt = tmp.Mul(1 / length)
}
return pt, 0.5 * length // Since tmp == 2 * (x.Cross(y))
}
/*
// intersectionSimple is not used by the C++ so it is skipped here.
*/
// projection returns the projection of aNorm onto X (x.Dot(aNorm)), and a bound
// on the error in the result. aNorm is not necessarily unit length.
//
// The remaining parameters (the length of aNorm (aNormLen) and the edge endpoints
// a0 and a1) allow this dot product to be computed more accurately and efficiently.
func projection(x, aNorm r3.Vector, aNormLen float64, a0, a1 Point) (proj, bound float64) {
// The error in the dot product is proportional to the lengths of the input
// vectors, so rather than using x itself (a unit-length vector) we use
// the vectors from x to the closer of the two edge endpoints. This
// typically reduces the error by a huge factor.
x0 := x.Sub(a0.Vector)
x1 := x.Sub(a1.Vector)
x0Dist2 := x0.Norm2()
x1Dist2 := x1.Norm2()
// If both distances are the same, we need to be careful to choose one
// endpoint deterministically so that the result does not change if the
// order of the endpoints is reversed.
var dist float64
if x0Dist2 < x1Dist2 || (x0Dist2 == x1Dist2 && x0.Cmp(x1) == -1) {
dist = math.Sqrt(x0Dist2)
proj = x0.Dot(aNorm)
} else {
dist = math.Sqrt(x1Dist2)
proj = x1.Dot(aNorm)
}
// This calculation bounds the error from all sources: the computation of
// the normal, the subtraction of one endpoint, and the dot product itself.
// dblEpsilon appears because the input points are assumed to be
// normalized in double precision.
//
// For reference, the bounds that went into this calculation are:
// ||N'-N|| <= ((1 + 2 * sqrt(3))||N|| + 32 * sqrt(3) * dblEpsilon) * epsilon
// |(A.B)'-(A.B)| <= (1.5 * (A.B) + 1.5 * ||A|| * ||B||) * epsilon
// ||(X-Y)'-(X-Y)|| <= ||X-Y|| * epsilon
bound = (((3.5+2*math.Sqrt(3))*aNormLen+32*math.Sqrt(3)*dblEpsilon)*dist + 1.5*math.Abs(proj)) * epsilon
return proj, bound
}
// compareEdges reports whether (a0,a1) is less than (b0,b1) with respect to a total
// ordering on edges that is invariant under edge reversals.
func compareEdges(a0, a1, b0, b1 Point) bool {
if a0.Cmp(a1.Vector) != -1 {
a0, a1 = a1, a0
}
if b0.Cmp(b1.Vector) != -1 {
b0, b1 = b1, b0
}
return a0.Cmp(b0.Vector) == -1 || (a0 == b0 && b0.Cmp(b1.Vector) == -1)
}
// intersectionStable returns the intersection point of the edges (a0,a1) and
// (b0,b1) if it can be computed to within an error of at most intersectionError
// by this function.
//
// The intersection point is not guaranteed to have the correct sign because we
// choose to use the longest of the two edges first. The sign is corrected by
// Intersection.
func intersectionStable(a0, a1, b0, b1 Point) (Point, bool) {
// Sort the two edges so that (a0,a1) is longer, breaking ties in a
// deterministic way that does not depend on the ordering of the endpoints.
// This is desirable for two reasons:
// - So that the result doesn't change when edges are swapped or reversed.
// - It reduces error, since the first edge is used to compute the edge
// normal (where a longer edge means less error), and the second edge
// is used for interpolation (where a shorter edge means less error).
aLen2 := a1.Sub(a0.Vector).Norm2()
bLen2 := b1.Sub(b0.Vector).Norm2()
if aLen2 < bLen2 || (aLen2 == bLen2 && compareEdges(a0, a1, b0, b1)) {
return intersectionStableSorted(b0, b1, a0, a1)
}
return intersectionStableSorted(a0, a1, b0, b1)
}
// intersectionStableSorted is a helper function for intersectionStable.
// It expects that the edges (a0,a1) and (b0,b1) have been sorted so that
// the first edge passed in is longer.
func intersectionStableSorted(a0, a1, b0, b1 Point) (Point, bool) {
var pt Point
// Compute the normal of the plane through (a0, a1) in a stable way.
aNorm := a0.Sub(a1.Vector).Cross(a0.Add(a1.Vector))
aNormLen := aNorm.Norm()
bLen := b1.Sub(b0.Vector).Norm()
// Compute the projection (i.e., signed distance) of b0 and b1 onto the
// plane through (a0, a1). Distances are scaled by the length of aNorm.
b0Dist, b0Error := projection(b0.Vector, aNorm, aNormLen, a0, a1)
b1Dist, b1Error := projection(b1.Vector, aNorm, aNormLen, a0, a1)
// The total distance from b0 to b1 measured perpendicularly to (a0,a1) is
// |b0Dist - b1Dist|. Note that b0Dist and b1Dist generally have
// opposite signs because b0 and b1 are on opposite sides of (a0, a1). The
// code below finds the intersection point by interpolating along the edge
// (b0, b1) to a fractional distance of b0Dist / (b0Dist - b1Dist).
//
// It can be shown that the maximum error in the interpolation fraction is
//
// (b0Dist * b1Error - b1Dist * b0Error) / (distSum * (distSum - errorSum))
//
// We save ourselves some work by scaling the result and the error bound by
// "distSum", since the result is normalized to be unit length anyway.
distSum := math.Abs(b0Dist - b1Dist)
errorSum := b0Error + b1Error
if distSum <= errorSum {
return pt, false // Error is unbounded in this case.
}
x := b1.Mul(b0Dist).Sub(b0.Mul(b1Dist))
err := bLen*math.Abs(b0Dist*b1Error-b1Dist*b0Error)/
(distSum-errorSum) + 2*distSum*epsilon
// Finally we normalize the result, compute the corresponding error, and
// check whether the total error is acceptable.
xLen := x.Norm()
maxError := intersectionError
if err > (float64(maxError)-epsilon)*xLen {
return pt, false
}
return Point{x.Mul(1 / xLen)}, true
}
// intersectionExact returns the intersection point of (a0, a1) and (b0, b1)
// using precise arithmetic. Note that the result is not exact because it is
// rounded down to double precision at the end. Also, the intersection point
// is not guaranteed to have the correct sign (i.e., the return value may need
// to be negated).
func intersectionExact(a0, a1, b0, b1 Point) Point {
// Since we are using presice arithmetic, we don't need to worry about
// numerical stability.
a0P := r3.PreciseVectorFromVector(a0.Vector)
a1P := r3.PreciseVectorFromVector(a1.Vector)
b0P := r3.PreciseVectorFromVector(b0.Vector)
b1P := r3.PreciseVectorFromVector(b1.Vector)
aNormP := a0P.Cross(a1P)
bNormP := b0P.Cross(b1P)
xP := aNormP.Cross(bNormP)
// The final Normalize() call is done in double precision, which creates a
// directional error of up to 2*dblEpsilon. (Precise conversion and Normalize()
// each contribute up to dblEpsilon of directional error.)
x := xP.Vector()
if x == (r3.Vector{}) {
// The two edges are exactly collinear, but we still consider them to be
// "crossing" because of simulation of simplicity. Out of the four
// endpoints, exactly two lie in the interior of the other edge. Of
// those two we return the one that is lexicographically smallest.
x = r3.Vector{10, 10, 10} // Greater than any valid S2Point
aNorm := Point{aNormP.Vector()}
bNorm := Point{bNormP.Vector()}
if OrderedCCW(b0, a0, b1, bNorm) && a0.Cmp(x) == -1 {
return a0
}
if OrderedCCW(b0, a1, b1, bNorm) && a1.Cmp(x) == -1 {
return a1
}
if OrderedCCW(a0, b0, a1, aNorm) && b0.Cmp(x) == -1 {
return b0
}
if OrderedCCW(a0, b1, a1, aNorm) && b1.Cmp(x) == -1 {
return b1
}
}
return Point{x}
}

View file

@ -1,378 +0,0 @@
// Copyright 2017 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 a collection of methods for computing the distance to an edge,
// interpolating along an edge, projecting points onto edges, etc.
import (
"math"
"github.com/golang/geo/s1"
)
// DistanceFromSegment returns the distance of point X from line segment AB.
// The points are expected to be normalized. The result is very accurate for small
// distances but may have some numerical error if the distance is large
// (approximately pi/2 or greater). The case A == B is handled correctly.
func DistanceFromSegment(x, a, b Point) s1.Angle {
var minDist s1.ChordAngle
minDist, _ = updateMinDistance(x, a, b, minDist, true)
return minDist.Angle()
}
// IsDistanceLess reports whether the distance from X to the edge AB is less
// than limit. This method is faster than DistanceFromSegment(). If you want to
// compare against a fixed s1.Angle, you should convert it to an s1.ChordAngle
// once and save the value, since this conversion is relatively expensive.
func IsDistanceLess(x, a, b Point, limit s1.ChordAngle) bool {
_, less := UpdateMinDistance(x, a, b, limit)
return less
}
// UpdateMinDistance checks if the distance from X to the edge AB is less
// than minDist, and if so, returns the updated value and true.
// The case A == B is handled correctly.
//
// Use this method when you want to compute many distances and keep track of
// the minimum. It is significantly faster than using DistanceFromSegment
// because (1) using s1.ChordAngle is much faster than s1.Angle, and (2) it
// can save a lot of work by not actually computing the distance when it is
// obviously larger than the current minimum.
func UpdateMinDistance(x, a, b Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
return updateMinDistance(x, a, b, minDist, false)
}
// UpdateMaxDistance checks if the distance from X to the edge AB is greater
// than maxDist, and if so, returns the updated value and true.
// Otherwise it returns false. The case A == B is handled correctly.
func UpdateMaxDistance(x, a, b Point, maxDist s1.ChordAngle) (s1.ChordAngle, bool) {
dist := maxChordAngle(ChordAngleBetweenPoints(x, a), ChordAngleBetweenPoints(x, b))
if dist > s1.RightChordAngle {
dist, _ = updateMinDistance(Point{x.Mul(-1)}, a, b, dist, true)
dist = s1.StraightChordAngle - dist
}
if maxDist < dist {
return dist, true
}
return maxDist, false
}
// IsInteriorDistanceLess reports whether the minimum distance from X to the
// edge AB is attained at an interior point of AB (i.e., not an endpoint), and
// that distance is less than limit.
func IsInteriorDistanceLess(x, a, b Point, limit s1.ChordAngle) bool {
_, less := UpdateMinInteriorDistance(x, a, b, limit)
return less
}
// UpdateMinInteriorDistance reports whether the minimum distance from X to AB
// is attained at an interior point of AB (i.e., not an endpoint), and that distance
// is less than minDist. If so, the value of minDist is updated and true is returned.
// Otherwise it is unchanged and returns false.
func UpdateMinInteriorDistance(x, a, b Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
return interiorDist(x, a, b, minDist, false)
}
// Project returns the point along the edge AB that is closest to the point X.
// The fractional distance of this point along the edge AB can be obtained
// using DistanceFraction.
//
// This requires that all points are unit length.
func Project(x, a, b Point) Point {
aXb := a.PointCross(b)
// Find the closest point to X along the great circle through AB.
p := x.Sub(aXb.Mul(x.Dot(aXb.Vector) / aXb.Vector.Norm2()))
// If this point is on the edge AB, then it's the closest point.
if Sign(aXb, a, Point{p}) && Sign(Point{p}, b, aXb) {
return Point{p.Normalize()}
}
// Otherwise, the closest point is either A or B.
if x.Sub(a.Vector).Norm2() <= x.Sub(b.Vector).Norm2() {
return a
}
return b
}
// DistanceFraction returns the distance ratio of the point X along an edge AB.
// If X is on the line segment AB, this is the fraction T such
// that X == Interpolate(T, A, B).
//
// This requires that A and B are distinct.
func DistanceFraction(x, a, b Point) float64 {
d0 := x.Angle(a.Vector)
d1 := x.Angle(b.Vector)
return float64(d0 / (d0 + d1))
}
// Interpolate returns the point X along the line segment AB whose distance from A
// is the given fraction "t" of the distance AB. Does NOT require that "t" be
// between 0 and 1. Note that all distances are measured on the surface of
// the sphere, so this is more complicated than just computing (1-t)*a + t*b
// and normalizing the result.
func Interpolate(t float64, a, b Point) Point {
if t == 0 {
return a
}
if t == 1 {
return b
}
ab := a.Angle(b.Vector)
return InterpolateAtDistance(s1.Angle(t)*ab, a, b)
}
// InterpolateAtDistance returns the point X along the line segment AB whose
// distance from A is the angle ax.
func InterpolateAtDistance(ax s1.Angle, a, b Point) Point {
aRad := ax.Radians()
// Use PointCross to compute the tangent vector at A towards B. The
// result is always perpendicular to A, even if A=B or A=-B, but it is not
// necessarily unit length. (We effectively normalize it below.)
normal := a.PointCross(b)
tangent := normal.Vector.Cross(a.Vector)
// Now compute the appropriate linear combination of A and "tangent". With
// infinite precision the result would always be unit length, but we
// normalize it anyway to ensure that the error is within acceptable bounds.
// (Otherwise errors can build up when the result of one interpolation is
// fed into another interpolation.)
return Point{(a.Mul(math.Cos(aRad)).Add(tangent.Mul(math.Sin(aRad) / tangent.Norm()))).Normalize()}
}
// minUpdateDistanceMaxError returns the maximum error in the result of
// UpdateMinDistance (and the associated functions such as
// UpdateMinInteriorDistance, IsDistanceLess, etc), assuming that all
// input points are normalized to within the bounds guaranteed by r3.Vector's
// Normalize. The error can be added or subtracted from an s1.ChordAngle
// using its Expanded method.
func minUpdateDistanceMaxError(dist s1.ChordAngle) float64 {
// There are two cases for the maximum error in UpdateMinDistance(),
// depending on whether the closest point is interior to the edge.
return math.Max(minUpdateInteriorDistanceMaxError(dist), dist.MaxPointError())
}
// minUpdateInteriorDistanceMaxError returns the maximum error in the result of
// UpdateMinInteriorDistance, assuming that all input points are normalized
// to within the bounds guaranteed by Point's Normalize. The error can be added
// or subtracted from an s1.ChordAngle using its Expanded method.
//
// Note that accuracy goes down as the distance approaches 0 degrees or 180
// degrees (for different reasons). Near 0 degrees the error is acceptable
// for all practical purposes (about 1.2e-15 radians ~= 8 nanometers). For
// exactly antipodal points the maximum error is quite high (0.5 meters),
// but this error drops rapidly as the points move away from antipodality
// (approximately 1 millimeter for points that are 50 meters from antipodal,
// and 1 micrometer for points that are 50km from antipodal).
//
// TODO(roberts): Currently the error bound does not hold for edges whose endpoints
// are antipodal to within about 1e-15 radians (less than 1 micron). This could
// be fixed by extending PointCross to use higher precision when necessary.
func minUpdateInteriorDistanceMaxError(dist s1.ChordAngle) float64 {
// If a point is more than 90 degrees from an edge, then the minimum
// distance is always to one of the endpoints, not to the edge interior.
if dist >= s1.RightChordAngle {
return 0.0
}
// This bound includes all source of error, assuming that the input points
// are normalized. a and b are components of chord length that are
// perpendicular and parallel to a plane containing the edge respectively.
b := math.Min(1.0, 0.5*float64(dist)*float64(dist))
a := math.Sqrt(b * (2 - b))
return ((2.5+2*math.Sqrt(3)+8.5*a)*a +
(2+2*math.Sqrt(3)/3+6.5*(1-b))*b +
(23+16/math.Sqrt(3))*dblEpsilon) * dblEpsilon
}
// updateMinDistance computes the distance from a point X to a line segment AB,
// and if either the distance was less than the given minDist, or alwaysUpdate is
// true, the value and whether it was updated are returned.
func updateMinDistance(x, a, b Point, minDist s1.ChordAngle, alwaysUpdate bool) (s1.ChordAngle, bool) {
if d, ok := interiorDist(x, a, b, minDist, alwaysUpdate); ok {
// Minimum distance is attained along the edge interior.
return d, true
}
// Otherwise the minimum distance is to one of the endpoints.
xa2, xb2 := (x.Sub(a.Vector)).Norm2(), x.Sub(b.Vector).Norm2()
dist := s1.ChordAngle(math.Min(xa2, xb2))
if !alwaysUpdate && dist >= minDist {
return minDist, false
}
return dist, true
}
// interiorDist returns the shortest distance from point x to edge ab, assuming
// that the closest point to X is interior to AB. If the closest point is not
// interior to AB, interiorDist returns (minDist, false). If alwaysUpdate is set to
// false, the distance is only updated when the value exceeds certain the given minDist.
func interiorDist(x, a, b Point, minDist s1.ChordAngle, alwaysUpdate bool) (s1.ChordAngle, bool) {
// Chord distance of x to both end points a and b.
xa2, xb2 := (x.Sub(a.Vector)).Norm2(), x.Sub(b.Vector).Norm2()
// The closest point on AB could either be one of the two vertices (the
// vertex case) or in the interior (the interior case). Let C = A x B.
// If X is in the spherical wedge extending from A to B around the axis
// through C, then we are in the interior case. Otherwise we are in the
// vertex case.
//
// Check whether we might be in the interior case. For this to be true, XAB
// and XBA must both be acute angles. Checking this condition exactly is
// expensive, so instead we consider the planar triangle ABX (which passes
// through the sphere's interior). The planar angles XAB and XBA are always
// less than the corresponding spherical angles, so if we are in the
// interior case then both of these angles must be acute.
//
// We check this by computing the squared edge lengths of the planar
// triangle ABX, and testing acuteness using the law of cosines:
//
// max(XA^2, XB^2) < min(XA^2, XB^2) + AB^2
if math.Max(xa2, xb2) >= math.Min(xa2, xb2)+(a.Sub(b.Vector)).Norm2() {
return minDist, false
}
// The minimum distance might be to a point on the edge interior. Let R
// be closest point to X that lies on the great circle through AB. Rather
// than computing the geodesic distance along the surface of the sphere,
// instead we compute the "chord length" through the sphere's interior.
//
// The squared chord length XR^2 can be expressed as XQ^2 + QR^2, where Q
// is the point X projected onto the plane through the great circle AB.
// The distance XQ^2 can be written as (X.C)^2 / |C|^2 where C = A x B.
// We ignore the QR^2 term and instead use XQ^2 as a lower bound, since it
// is faster and the corresponding distance on the Earth's surface is
// accurate to within 1% for distances up to about 1800km.
c := a.PointCross(b)
c2 := c.Norm2()
xDotC := x.Dot(c.Vector)
xDotC2 := xDotC * xDotC
if !alwaysUpdate && xDotC2 >= c2*float64(minDist) {
// The closest point on the great circle AB is too far away.
return minDist, false
}
// Otherwise we do the exact, more expensive test for the interior case.
// This test is very likely to succeed because of the conservative planar
// test we did initially.
cx := c.Cross(x.Vector)
if a.Dot(cx) >= 0 || b.Dot(cx) <= 0 {
return minDist, false
}
// Compute the squared chord length XR^2 = XQ^2 + QR^2 (see above).
// This calculation has good accuracy for all chord lengths since it
// is based on both the dot product and cross product (rather than
// deriving one from the other). However, note that the chord length
// representation itself loses accuracy as the angle approaches π.
qr := 1 - math.Sqrt(cx.Norm2()/c2)
dist := s1.ChordAngle((xDotC2 / c2) + (qr * qr))
if !alwaysUpdate && dist >= minDist {
return minDist, false
}
return dist, true
}
// updateEdgePairMinDistance computes the minimum distance between the given
// pair of edges. If the two edges cross, the distance is zero. The cases
// a0 == a1 and b0 == b1 are handled correctly.
func updateEdgePairMinDistance(a0, a1, b0, b1 Point, minDist s1.ChordAngle) (s1.ChordAngle, bool) {
if minDist == 0 {
return 0, false
}
if CrossingSign(a0, a1, b0, b1) == Cross {
minDist = 0
return 0, true
}
// Otherwise, the minimum distance is achieved at an endpoint of at least
// one of the two edges. We ensure that all four possibilities are always checked.
//
// The calculation below computes each of the six vertex-vertex distances
// twice (this could be optimized).
var ok1, ok2, ok3, ok4 bool
minDist, ok1 = UpdateMinDistance(a0, b0, b1, minDist)
minDist, ok2 = UpdateMinDistance(a1, b0, b1, minDist)
minDist, ok3 = UpdateMinDistance(b0, a0, a1, minDist)
minDist, ok4 = UpdateMinDistance(b1, a0, a1, minDist)
return minDist, ok1 || ok2 || ok3 || ok4
}
// updateEdgePairMaxDistance reports the minimum distance between the given pair of edges.
// If one edge crosses the antipodal reflection of the other, the distance is pi.
func updateEdgePairMaxDistance(a0, a1, b0, b1 Point, maxDist s1.ChordAngle) (s1.ChordAngle, bool) {
if maxDist == s1.StraightChordAngle {
return s1.StraightChordAngle, false
}
if CrossingSign(a0, a1, Point{b0.Mul(-1)}, Point{b1.Mul(-1)}) == Cross {
return s1.StraightChordAngle, true
}
// Otherwise, the maximum distance is achieved at an endpoint of at least
// one of the two edges. We ensure that all four possibilities are always checked.
//
// The calculation below computes each of the six vertex-vertex distances
// twice (this could be optimized).
var ok1, ok2, ok3, ok4 bool
maxDist, ok1 = UpdateMaxDistance(a0, b0, b1, maxDist)
maxDist, ok2 = UpdateMaxDistance(a1, b0, b1, maxDist)
maxDist, ok3 = UpdateMaxDistance(b0, a0, a1, maxDist)
maxDist, ok4 = UpdateMaxDistance(b1, a0, a1, maxDist)
return maxDist, ok1 || ok2 || ok3 || ok4
}
// EdgePairClosestPoints returns the pair of points (a, b) that achieves the
// minimum distance between edges a0a1 and b0b1, where a is a point on a0a1 and
// b is a point on b0b1. If the two edges intersect, a and b are both equal to
// the intersection point. Handles a0 == a1 and b0 == b1 correctly.
func EdgePairClosestPoints(a0, a1, b0, b1 Point) (Point, Point) {
if CrossingSign(a0, a1, b0, b1) == Cross {
x := Intersection(a0, a1, b0, b1)
return x, x
}
// We save some work by first determining which vertex/edge pair achieves
// the minimum distance, and then computing the closest point on that edge.
var minDist s1.ChordAngle
var ok bool
minDist, ok = updateMinDistance(a0, b0, b1, minDist, true)
closestVertex := 0
if minDist, ok = UpdateMinDistance(a1, b0, b1, minDist); ok {
closestVertex = 1
}
if minDist, ok = UpdateMinDistance(b0, a0, a1, minDist); ok {
closestVertex = 2
}
if minDist, ok = UpdateMinDistance(b1, a0, a1, minDist); ok {
closestVertex = 3
}
switch closestVertex {
case 0:
return a0, Project(a0, b0, b1)
case 1:
return a1, Project(a1, b0, b1)
case 2:
return Project(b0, a0, a1), b0
case 3:
return Project(b1, a0, a1), b1
default:
panic("illegal case reached")
}
}

View file

@ -1,237 +0,0 @@
// Copyright 2017 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 (
"encoding/binary"
"io"
)
const (
// encodingVersion is the current version of the encoding
// format that is compatible with C++ and other S2 libraries.
encodingVersion = int8(1)
// encodingCompressedVersion is the current version of the
// compressed format.
encodingCompressedVersion = int8(4)
)
// encoder handles the specifics of encoding for S2 types.
type encoder struct {
w io.Writer // the real writer passed to Encode
err error
}
func (e *encoder) writeUvarint(x uint64) {
if e.err != nil {
return
}
var buf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(buf[:], x)
_, e.err = e.w.Write(buf[:n])
}
func (e *encoder) writeBool(x bool) {
if e.err != nil {
return
}
var val int8
if x {
val = 1
}
e.err = binary.Write(e.w, binary.LittleEndian, val)
}
func (e *encoder) writeInt8(x int8) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeInt16(x int16) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeInt32(x int32) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeInt64(x int64) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeUint8(x uint8) {
if e.err != nil {
return
}
_, e.err = e.w.Write([]byte{x})
}
func (e *encoder) writeUint32(x uint32) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeUint64(x uint64) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeFloat32(x float32) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
func (e *encoder) writeFloat64(x float64) {
if e.err != nil {
return
}
e.err = binary.Write(e.w, binary.LittleEndian, x)
}
type byteReader interface {
io.Reader
io.ByteReader
}
// byteReaderAdapter embellishes an io.Reader with a ReadByte method,
// so that it implements the io.ByteReader interface.
type byteReaderAdapter struct {
io.Reader
}
func (b byteReaderAdapter) ReadByte() (byte, error) {
buf := []byte{0}
_, err := io.ReadFull(b, buf)
return buf[0], err
}
func asByteReader(r io.Reader) byteReader {
if br, ok := r.(byteReader); ok {
return br
}
return byteReaderAdapter{r}
}
type decoder struct {
r byteReader // the real reader passed to Decode
err error
}
func (d *decoder) readBool() (x bool) {
if d.err != nil {
return
}
var val int8
d.err = binary.Read(d.r, binary.LittleEndian, &val)
return val == 1
}
func (d *decoder) readInt8() (x int8) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readInt16() (x int16) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readInt32() (x int32) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readInt64() (x int64) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readUint8() (x uint8) {
if d.err != nil {
return
}
x, d.err = d.r.ReadByte()
return
}
func (d *decoder) readUint32() (x uint32) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readUint64() (x uint64) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readFloat32() (x float32) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readFloat64() (x float64) {
if d.err != nil {
return
}
d.err = binary.Read(d.r, binary.LittleEndian, &x)
return
}
func (d *decoder) readUvarint() (x uint64) {
if d.err != nil {
return
}
x, d.err = binary.ReadUvarint(d.r)
return
}

Some files were not shown because too many files have changed in this diff Show more