blob: f367ee32d0a0a8d12cc9d240394411c872a1ba0e [file] [log] [blame]
// Copyright ©2019 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package kdtree_test
import (
"fmt"
"math"
"gonum.org/v1/gonum/spatial/kdtree"
)
func Example_accessiblePublicTransport() {
// Construct a k-d tree of train station locations
// to identify accessible public transport for the
// elderly.
t := kdtree.New(stations, false)
// Residence.
q := place{lat: 51.501476, lon: -0.140634}
var keep kdtree.Keeper
// Find all stations within 0.75 of the residence.
keep = kdtree.NewDistKeeper(0.75 * 0.75) // Distances are squared.
t.NearestSet(keep, q)
fmt.Println(`Stations within 750 m of 51.501476N 0.140634W.`)
for _, c := range keep.(*kdtree.DistKeeper).Heap {
p := c.Comparable.(place)
fmt.Printf("%s: %0.3f km\n", p.name, math.Sqrt(p.Distance(q)))
}
fmt.Println()
// Find the five closest stations to the residence.
keep = kdtree.NewNKeeper(5)
t.NearestSet(keep, q)
fmt.Println(`5 closest stations to 51.501476N 0.140634W.`)
for _, c := range keep.(*kdtree.NKeeper).Heap {
p := c.Comparable.(place)
fmt.Printf("%s: %0.3f km\n", p.name, math.Sqrt(p.Distance(q)))
}
// Output:
//
// Stations within 750 m of 51.501476N 0.140634W.
// St. James's Park: 0.545 km
// Green Park: 0.600 km
// Victoria: 0.621 km
//
// 5 closest stations to 51.501476N 0.140634W.
// St. James's Park: 0.545 km
// Green Park: 0.600 km
// Victoria: 0.621 km
// Hyde Park Corner: 0.846 km
// Picadilly Circus: 1.027 km
}
// stations is a list of railways stations satisfying the
// kdtree.Interface.
var stations = places{
{name: "Bond Street", lat: 51.5142, lon: -0.1494},
{name: "Charing Cross", lat: 51.508, lon: -0.1247},
{name: "Covent Garden", lat: 51.5129, lon: -0.1243},
{name: "Embankment", lat: 51.5074, lon: -0.1223},
{name: "Green Park", lat: 51.5067, lon: -0.1428},
{name: "Hyde Park Corner", lat: 51.5027, lon: -0.1527},
{name: "Leicester Square", lat: 51.5113, lon: -0.1281},
{name: "Marble Arch", lat: 51.5136, lon: -0.1586},
{name: "Oxford Circus", lat: 51.515, lon: -0.1415},
{name: "Picadilly Circus", lat: 51.5098, lon: -0.1342},
{name: "Pimlico", lat: 51.4893, lon: -0.1334},
{name: "Sloane Square", lat: 51.4924, lon: -0.1565},
{name: "South Kensington", lat: 51.4941, lon: -0.1738},
{name: "St. James's Park", lat: 51.4994, lon: -0.1335},
{name: "Temple", lat: 51.5111, lon: -0.1141},
{name: "Tottenham Court Road", lat: 51.5165, lon: -0.131},
{name: "Vauxhall", lat: 51.4861, lon: -0.1253},
{name: "Victoria", lat: 51.4965, lon: -0.1447},
{name: "Waterloo", lat: 51.5036, lon: -0.1143},
{name: "Westminster", lat: 51.501, lon: -0.1254},
}
// place is a kdtree.Comparable implementations.
type place struct {
name string
lat, lon float64
}
// Compare satisfies the axis comparisons method of the kdtree.Comparable interface.
// The dimensions are:
// 0 = lat
// 1 = lon
func (p place) Compare(c kdtree.Comparable, d kdtree.Dim) float64 {
q := c.(place)
switch d {
case 0:
return p.lat - q.lat
case 1:
return p.lon - q.lon
default:
panic("illegal dimension")
}
}
// Dims returns the number of dimensions to be considered.
func (p place) Dims() int { return 2 }
// Distance returns the distance between the receiver and c.
func (p place) Distance(c kdtree.Comparable) float64 {
q := c.(place)
d := haversine(p.lat, p.lon, q.lat, q.lon)
return d * d
}
// haversine returns the distance between two geographic coordinates.
func haversine(lat1, lon1, lat2, lon2 float64) float64 {
const r = 6371 // km
sdLat := math.Sin(radians(lat2-lat1) / 2)
sdLon := math.Sin(radians(lon2-lon1) / 2)
a := sdLat*sdLat + math.Cos(radians(lat1))*math.Cos(radians(lat2))*sdLon*sdLon
d := 2 * r * math.Asin(math.Sqrt(a))
return d // km
}
func radians(d float64) float64 {
return d * math.Pi / 180
}
// places is a collection of the place type that satisfies kdtree.Interface.
type places []place
func (p places) Index(i int) kdtree.Comparable { return p[i] }
func (p places) Len() int { return len(p) }
func (p places) Pivot(d kdtree.Dim) int { return plane{places: p, Dim: d}.Pivot() }
func (p places) Slice(start, end int) kdtree.Interface { return p[start:end] }
// plane is required to help places.
type plane struct {
kdtree.Dim
places
}
func (p plane) Less(i, j int) bool {
switch p.Dim {
case 0:
return p.places[i].lat < p.places[j].lat
case 1:
return p.places[i].lon < p.places[j].lon
default:
panic("illegal dimension")
}
}
func (p plane) Pivot() int { return kdtree.Partition(p, kdtree.MedianOfMedians(p)) }
func (p plane) Slice(start, end int) kdtree.SortSlicer {
p.places = p.places[start:end]
return p
}
func (p plane) Swap(i, j int) {
p.places[i], p.places[j] = p.places[j], p.places[i]
}