Compare commits
15 Commits
4c5d4e805c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d53b0639b | ||
|
|
c8066d7b17 | ||
|
|
582e890dc4 | ||
|
|
1b4d33b7ce | ||
|
|
bac048a685 | ||
|
|
5101525607 | ||
|
|
cc74cc60f2 | ||
|
|
bcdfacc142 | ||
|
|
7f379e9d90 | ||
|
|
68f7ad68c6 | ||
|
|
ab4c577825 | ||
|
|
a05ee6de8f | ||
|
|
8fd7c0196d | ||
|
|
55a78add6f | ||
|
|
2662e056e0 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/turbomaps-er
|
||||||
20
.mise/tasks/search
Executable file
20
.mise/tasks/search
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
#MISE description="TODO"
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "${MISE_PROJECT_ROOT}"
|
||||||
|
|
||||||
|
cities=()
|
||||||
|
for city in "$@"; do
|
||||||
|
cities+=("-t=$city")
|
||||||
|
done
|
||||||
|
searches=(
|
||||||
|
"-s=mobile home park=0.5"
|
||||||
|
"-s=schools=0.25"
|
||||||
|
"-s=water treatment facility=1.0"
|
||||||
|
"-s=landfill=1.5"
|
||||||
|
"-s=police department=0.1"
|
||||||
|
"-s=prison=2.5"
|
||||||
|
)
|
||||||
|
go run ./ "${searches[@]}" -d "${cities[@]}"
|
||||||
24
cmd/maps.go
24
cmd/maps.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ type Location struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Maps) textSearchOne(ctx context.Context, query string) (Location, error) {
|
func (m *Maps) textSearchOne(ctx context.Context, query string) (Location, error) {
|
||||||
results, err := m.Search(ctx, query)
|
results, err := m.Search(ctx, query, 1.0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Location{}, err
|
return Location{}, err
|
||||||
} else if len(results) < 1 {
|
} else if len(results) < 1 {
|
||||||
@@ -47,7 +48,26 @@ func (m *Maps) textSearchOne(ctx context.Context, query string) (Location, error
|
|||||||
return results[0], nil
|
return results[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Maps) Search(ctx context.Context, query string) ([]Location, error) {
|
var convertToMiles = 69.0
|
||||||
|
|
||||||
|
func (m *Maps) Search(ctx context.Context, query string, radius_miles float64) ([]Location, error) {
|
||||||
|
results, err := m.search(ctx, query)
|
||||||
|
for i := len(results) - 1; i >= 0; i-- {
|
||||||
|
shouldKeep := true
|
||||||
|
if m.around != (Location{}) {
|
||||||
|
a := m.around.Lat - results[i].Lat
|
||||||
|
b := m.around.Lng - results[i].Lng
|
||||||
|
dist := math.Sqrt(a*a + b*b)
|
||||||
|
shouldKeep = dist*convertToMiles < radius_miles
|
||||||
|
}
|
||||||
|
if !shouldKeep {
|
||||||
|
results = append(results[:i], results[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Maps) search(ctx context.Context, query string) ([]Location, error) {
|
||||||
cacheK := path.Join(fmt.Sprint(m.around), query)
|
cacheK := path.Join(fmt.Sprint(m.around), query)
|
||||||
if b, err := m.cache.Get(ctx, cacheK); err == nil {
|
if b, err := m.cache.Get(ctx, cacheK); err == nil {
|
||||||
var locations []Location
|
var locations []Location
|
||||||
|
|||||||
142
cmd/run.go
142
cmd/run.go
@@ -3,23 +3,159 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Run(ctx context.Context) error {
|
func Run(ctx context.Context) error {
|
||||||
m, err := NewMapsOf(ctx, os.Args[1])
|
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||||
|
searchRadius := fs.Float64("r", 5.0, "search radius in miles")
|
||||||
|
doArea := fs.Bool("d", false, "do result radius in miles")
|
||||||
|
var towns FlagStringArray
|
||||||
|
fs.Var(&towns, "t", "list of towns to search around")
|
||||||
|
searches := StringToFloat64{}
|
||||||
|
fs.Var(&searches, "s", "list of things to search for around each town")
|
||||||
|
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type geoJson struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Properties struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"properties"`
|
||||||
|
Geometry struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Coordinates any `json:"coordinates"`
|
||||||
|
} `json:"geometry"`
|
||||||
|
}
|
||||||
|
type Area [1][5][2]float64
|
||||||
|
type Point [2]float64
|
||||||
|
geoJsons := []geoJson{}
|
||||||
|
for search, area := range searches {
|
||||||
|
results := []Location{}
|
||||||
|
for _, town := range towns {
|
||||||
|
m, err := NewMapsOf(ctx, town)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := m.Search(ctx, os.Args[2])
|
more, err := m.Search(ctx, search, *searchRadius)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
results = append(results, more...)
|
||||||
|
}
|
||||||
|
|
||||||
b, _ := json.Marshal(results)
|
slices.SortFunc(results, func(a, b Location) int {
|
||||||
|
return strings.Compare(a.Name, b.Name)
|
||||||
|
})
|
||||||
|
slices.CompactFunc(results, func(a, b Location) bool {
|
||||||
|
return a.Name == b.Name
|
||||||
|
})
|
||||||
|
radius := area / convertToMiles
|
||||||
|
radiusX := 2 * radius / 2
|
||||||
|
radiusY := 2 * radius / 3
|
||||||
|
|
||||||
|
for i := range results {
|
||||||
|
var a geoJson
|
||||||
|
a.Type = "Feature"
|
||||||
|
a.Properties.Name = path.Join(search, results[i].Name)
|
||||||
|
if strings.Contains(search, "school") {
|
||||||
|
a.Properties.Name = " "
|
||||||
|
}
|
||||||
|
a.Geometry.Type = "Point"
|
||||||
|
a.Geometry.Coordinates = Point{results[i].Lng, results[i].Lat}
|
||||||
|
if *doArea {
|
||||||
|
a.Geometry.Type = "Polygon"
|
||||||
|
x, y := results[i].Lng, results[i].Lat
|
||||||
|
a.Geometry.Coordinates = Area{{
|
||||||
|
[2]float64{x, y + radiusY}, // top
|
||||||
|
[2]float64{x + radiusX, y}, // right
|
||||||
|
[2]float64{x, y - radiusY}, // bot
|
||||||
|
[2]float64{x - radiusX, y}, // left
|
||||||
|
[2]float64{x, y + radiusY}, // top
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
geoJsons = append(geoJsons, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println(len(geoJsons))
|
||||||
|
slices.SortFunc(geoJsons, func(a, b geoJson) int {
|
||||||
|
return strings.Compare(fmt.Sprint(a.Geometry.Coordinates), fmt.Sprint(b.Geometry.Coordinates))
|
||||||
|
})
|
||||||
|
geoJsons = slices.CompactFunc(geoJsons, func(a, b geoJson) bool {
|
||||||
|
return fmt.Sprint(a.Geometry.Coordinates) == fmt.Sprint(b.Geometry.Coordinates)
|
||||||
|
})
|
||||||
|
if *doArea {
|
||||||
|
for i := range geoJsons {
|
||||||
|
areaI, ok := geoJsons[i].Geometry.Coordinates.(Area)
|
||||||
|
if ok {
|
||||||
|
areaI := areaI[0]
|
||||||
|
for j := i + 1; j < len(geoJsons); j++ {
|
||||||
|
areaJ, ok := geoJsons[j].Geometry.Coordinates.(Area)
|
||||||
|
if ok {
|
||||||
|
areaJ := areaJ[0]
|
||||||
|
// if diamond i contains j, then drop j
|
||||||
|
iAbove := areaI[0][1] > areaJ[0][1]
|
||||||
|
iRight := areaI[1][0] > areaJ[1][0]
|
||||||
|
iBelow := areaI[2][1] < areaJ[2][1]
|
||||||
|
iLeft := areaI[3][0] < areaJ[3][0]
|
||||||
|
if iAbove && iRight && iBelow && iLeft {
|
||||||
|
geoJsons[j].Geometry.Type = "Point"
|
||||||
|
geoJsons[j].Geometry.Coordinates = Point{
|
||||||
|
(areaJ[1][0] + areaJ[3][0]) / 2.0,
|
||||||
|
(areaJ[0][1] + areaJ[2][1]) / 2.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Println("COMPACTED", len(geoJsons))
|
||||||
|
b, _ := json.Marshal(map[string]any{
|
||||||
|
"features": geoJsons,
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
})
|
||||||
fmt.Printf("%s\n", b)
|
fmt.Printf("%s\n", b)
|
||||||
|
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FlagStringArray []string
|
||||||
|
|
||||||
|
func (array *FlagStringArray) String() string {
|
||||||
|
return strings.Join(*array, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (array *FlagStringArray) Set(s string) error {
|
||||||
|
*array = append(*array, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringToFloat64 map[string]float64
|
||||||
|
|
||||||
|
func (array *StringToFloat64) String() string {
|
||||||
|
return fmt.Sprintf("%+v", (*map[string]float64)(array))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (array *StringToFloat64) Set(s string) error {
|
||||||
|
idx := strings.Index(s, "=")
|
||||||
|
if idx < 0 {
|
||||||
|
return fmt.Errorf("should be formatted as k=v")
|
||||||
|
}
|
||||||
|
k := s[:idx]
|
||||||
|
v := s[idx+1:]
|
||||||
|
var n float64
|
||||||
|
if err := json.Unmarshal([]byte(v), &n); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
(*array)[k] = n
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
17
mise.toml
17
mise.toml
@@ -1,5 +1,18 @@
|
|||||||
[env]
|
[env]
|
||||||
GOOGLE_PLACES_API_KEY = "AIzaSyBkACm-LQkoSfsTO5_XAzBVZE9-JQzcNkg"
|
GOOGLE_PLACES_API_KEY = "AIzaSyBkACm-LQkoSfsTO5_XAzBVZE9-JQzcNkg"
|
||||||
|
CATEGORIES = "schools,dump,prison,water treatment,police department,college,trailer park,coffee roaster,board game store,costco,organic grocery store"
|
||||||
|
|
||||||
[tasks.default]
|
[tasks.sammy]
|
||||||
run = "cd \"${MISE_PROJECT_ROOT}\"; go run ./ 'olympia, wa' 'schools'"
|
run = "mise run search -- {sammamish,duvall,'cottage lake',issaquah,snohomish}', wa'"
|
||||||
|
|
||||||
|
[tasks.olympia]
|
||||||
|
run = "mise run search -- {olympia,lacey,tumwater}', wa'"
|
||||||
|
|
||||||
|
[tasks.bend]
|
||||||
|
run = "mise run search -- 'bend, or'"
|
||||||
|
|
||||||
|
[tasks.ridgefield]
|
||||||
|
run = "mise run search -- {ridgefield,vancouver,'mt vista','hazel dell',felida,'brush prarie','battle ground'}', or'"
|
||||||
|
|
||||||
|
[tasks.bellingham]
|
||||||
|
run = "mise run search -- {burlington,sedro-wooley,'mt vernon',bellingham,ferndale}', wa'"
|
||||||
|
|||||||
Reference in New Issue
Block a user