Compare commits

..

8 Commits

Author SHA1 Message Date
Bel LaPointe
2d53b0639b dedupe points across searches but not across towns 2026-03-17 12:39:55 -06:00
Bel LaPointe
c8066d7b17 overlappping squares become points 2026-03-17 12:33:53 -06:00
Bel LaPointe
582e890dc4 combine completely overlapped squares 2026-03-17 12:16:57 -06:00
Bel LaPointe
1b4d33b7ce bend 2026-03-17 12:07:53 -06:00
Bel LaPointe
bac048a685 helper 2026-03-15 00:27:39 -06:00
Bel LaPointe
5101525607 accept multi search too 2026-03-15 00:22:28 -06:00
Bel LaPointe
cc74cc60f2 multi town 2026-03-15 00:16:36 -06:00
Bel LaPointe
bcdfacc142 diamonds 2026-03-15 00:02:08 -06:00
4 changed files with 150 additions and 31 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/turbomaps-er

20
.mise/tasks/search Executable file
View 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[@]}"

View File

@@ -5,31 +5,24 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"log"
"os" "os"
"path" "path"
"slices"
"strings"
) )
func Run(ctx context.Context) error { func Run(ctx context.Context) error {
fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
searchRadius := fs.Float64("r", 5.0, "search radius in miles") searchRadius := fs.Float64("r", 5.0, "search radius in miles")
area := fs.Float64("a", 0.5, "result radius in miles")
doArea := fs.Bool("d", false, "do result 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 { if err := fs.Parse(os.Args[1:]); err != nil {
panic(err) panic(err)
} }
radius := *area / convertToMiles
radiusX := radius / 2
radiusY := radius / 3
m, err := NewMapsOf(ctx, fs.Args()[0])
if err != nil {
return err
}
results, err := m.Search(ctx, fs.Args()[1], *searchRadius)
if err != nil {
return err
}
type geoJson struct { type geoJson struct {
Type string `json:"type"` Type string `json:"type"`
@@ -38,27 +31,94 @@ func Run(ctx context.Context) error {
} `json:"properties"` } `json:"properties"`
Geometry struct { Geometry struct {
Type string `json:"type"` Type string `json:"type"`
Coordinates []any `json:"coordinates"` Coordinates any `json:"coordinates"`
} `json:"geometry"` } `json:"geometry"`
} }
geoJsons := make([]geoJson, len(results)) type Area [1][5][2]float64
for i := range results { type Point [2]float64
geoJsons[i].Type = "Feature" geoJsons := []geoJson{}
geoJsons[i].Properties.Name = path.Join(fs.Args()[1], results[i].Name) for search, area := range searches {
geoJsons[i].Geometry.Type = "Point" results := []Location{}
geoJsons[i].Geometry.Coordinates = []any{results[i].Lng, results[i].Lat} for _, town := range towns {
if *doArea { m, err := NewMapsOf(ctx, town)
geoJsons[i].Geometry.Type = "Polygon" if err != nil {
x, y := results[i].Lng, results[i].Lat return err
geoJsons[i].Geometry.Coordinates = []any{[]any{ }
[2]float64{x - radiusX, y + radiusY}, // top left
[2]float64{x + radiusX, y + radiusY}, // top righ more, err := m.Search(ctx, search, *searchRadius)
[2]float64{x + radiusX, y - radiusY}, // bot righ if err != nil {
[2]float64{x - radiusX, y - radiusY}, // bot left return err
[2]float64{x - radiusX, y + radiusY}, // top left }
}} results = append(results, more...)
}
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{ b, _ := json.Marshal(map[string]any{
"features": geoJsons, "features": geoJsons,
"type": "FeatureCollection", "type": "FeatureCollection",
@@ -67,3 +127,35 @@ func Run(ctx context.Context) error {
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
}

View File

@@ -8,5 +8,11 @@ run = "mise run search -- {sammamish,duvall,'cottage lake',issaquah,snohomish}',
[tasks.olympia] [tasks.olympia]
run = "mise run search -- {olympia,lacey,tumwater}', wa'" 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] [tasks.bellingham]
run = "mise run search -- {burlington,sedro-wooley,'mt vernon',bellingham,ferndale}', wa'" run = "mise run search -- {burlington,sedro-wooley,'mt vernon',bellingham,ferndale}', wa'"