148 lines
5.1 KiB
Go
148 lines
5.1 KiB
Go
|
|
package geojson_test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"b612.me/astro"
|
||
|
|
"b612.me/astro/geojson"
|
||
|
|
"b612.me/astro/mercury"
|
||
|
|
"b612.me/astro/venus"
|
||
|
|
)
|
||
|
|
|
||
|
|
type transitCollection struct {
|
||
|
|
TimeScale string `json:"time_scale"`
|
||
|
|
Features []struct {
|
||
|
|
Properties map[string]interface{} `json:"properties"`
|
||
|
|
Geometry struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Coordinates json.RawMessage `json:"coordinates"`
|
||
|
|
} `json:"geometry"`
|
||
|
|
} `json:"features"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func decodeTransit(t *testing.T, data []byte) (transitCollection, map[string]int) {
|
||
|
|
t.Helper()
|
||
|
|
var collection transitCollection
|
||
|
|
if err := json.Unmarshal(data, &collection); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
roles := map[string]int{}
|
||
|
|
for _, item := range collection.Features {
|
||
|
|
if item.Properties["event"] != "planet-transit" {
|
||
|
|
t.Fatalf("unexpected event %v", item.Properties["event"])
|
||
|
|
}
|
||
|
|
roles[item.Properties["role"].(string)]++
|
||
|
|
}
|
||
|
|
return collection, roles
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2032-11-13 水星凌日:等时线四个接触俱全,区域与地平线成对输出,凌甚点落在太阳直射点。
|
||
|
|
func TestMarshalMercuryTransit(t *testing.T) {
|
||
|
|
info := mercury.ClosestTransit(time.Date(2032, 11, 13, 0, 0, 0, 0, time.UTC))
|
||
|
|
data, err := geojson.MarshalMercuryTransit(info, geojson.PlanetTransitOptions{
|
||
|
|
IsochroneStep: 20 * time.Second,
|
||
|
|
TimeMarkers: &geojson.TimeMarkerOptions{Step: 30 * time.Minute},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
collection, roles := decodeTransit(t, data)
|
||
|
|
for _, role := range []string{
|
||
|
|
"visible-at-c1", "c1-horizon", "visible-at-c4", "c4-horizon",
|
||
|
|
"transit-entire", "transit-at-sunrise", "transit-at-sunset",
|
||
|
|
"c1-isochrone", "c2-isochrone", "c3-isochrone", "c4-isochrone", "greatest",
|
||
|
|
} {
|
||
|
|
if roles[role] != 1 {
|
||
|
|
t.Fatalf("role %s count = %d, want 1", role, roles[role])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if roles["time-marker"] < 8 {
|
||
|
|
t.Fatalf("time markers = %d, want one every 30 min over ~4.4 h", roles["time-marker"])
|
||
|
|
}
|
||
|
|
if collection.TimeScale != "" {
|
||
|
|
t.Fatal("UTC export must not carry time_scale")
|
||
|
|
}
|
||
|
|
for _, item := range collection.Features {
|
||
|
|
role := item.Properties["role"]
|
||
|
|
switch role {
|
||
|
|
case "c1-isochrone", "c2-isochrone", "c3-isochrone", "c4-isochrone":
|
||
|
|
var lines [][][]float64
|
||
|
|
if err := json.Unmarshal(item.Geometry.Coordinates, &lines); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
times := item.Properties["times"].([]interface{})
|
||
|
|
if len(times) != len(lines) || len(lines) == 0 {
|
||
|
|
t.Fatalf("%s: times %d do not align with %d lines", role, len(times), len(lines))
|
||
|
|
}
|
||
|
|
for index, line := range lines {
|
||
|
|
if len(times[index].([]interface{})) != len(line) {
|
||
|
|
t.Fatalf("%s line %d: times length mismatch", role, index)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
case "greatest":
|
||
|
|
if item.Properties["internal_start"] == nil || item.Properties["minimum_separation_arcsec"] == nil {
|
||
|
|
t.Fatal("greatest point must carry contact metadata")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 默认不出等时线;SkipRoles 跳过全部分区后只剩地平线、可见区与凌甚点。
|
||
|
|
func TestMarshalVenusTransitDefaultsAndSkip(t *testing.T) {
|
||
|
|
info := venus.ClosestTransit(time.Date(2012, 6, 6, 0, 0, 0, 0, time.UTC))
|
||
|
|
data, err := geojson.MarshalVenusTransit(info, geojson.PlanetTransitOptions{})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
_, roles := decodeTransit(t, data)
|
||
|
|
if roles["c1-isochrone"] != 0 || roles["transit-entire"] != 1 {
|
||
|
|
t.Fatalf("default roles = %v", roles)
|
||
|
|
}
|
||
|
|
data, err = geojson.MarshalVenusTransit(info, geojson.PlanetTransitOptions{
|
||
|
|
SkipRoles: []string{"transit-entire", "transit-at-sunrise", "transit-at-sunset"},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
_, roles = decodeTransit(t, data)
|
||
|
|
if len(roles) != 5 {
|
||
|
|
t.Fatalf("skipped region roles = %v", roles)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMarshalPlanetTransitValidation(t *testing.T) {
|
||
|
|
info := mercury.ClosestTransit(time.Date(2019, 11, 11, 0, 0, 0, 0, time.UTC))
|
||
|
|
if _, err := geojson.MarshalMercuryTransit(mercury.TransitInfo{}, geojson.PlanetTransitOptions{}); err == nil {
|
||
|
|
t.Fatal("invalid transit must be rejected")
|
||
|
|
}
|
||
|
|
if _, err := geojson.MarshalMercuryTransit(info, geojson.PlanetTransitOptions{IsochroneStep: time.Millisecond}); err == nil {
|
||
|
|
t.Fatal("sub-second isochrone step must be rejected")
|
||
|
|
}
|
||
|
|
swapped := info
|
||
|
|
swapped.Start, swapped.End = info.End, info.Start
|
||
|
|
if _, err := geojson.MarshalMercuryTransit(swapped, geojson.PlanetTransitOptions{}); err == nil {
|
||
|
|
t.Fatal("unordered contacts must be rejected")
|
||
|
|
}
|
||
|
|
inconsistent := info
|
||
|
|
inconsistent.HasInternal = false
|
||
|
|
if _, err := geojson.MarshalMercuryTransit(inconsistent, geojson.PlanetTransitOptions{IsochroneStep: time.Minute}); err == nil {
|
||
|
|
t.Fatal("internal contact times without HasInternal must be rejected")
|
||
|
|
}
|
||
|
|
if _, err := geojson.MarshalMercuryTransit(info, geojson.PlanetTransitOptions{
|
||
|
|
TimeMarkers: &geojson.TimeMarkerOptions{TimeScale: astro.TimeScaleUT1, Location: time.FixedZone("CST", 8*3600)},
|
||
|
|
}); err == nil {
|
||
|
|
t.Fatal("UT1 with a non-UTC location must be rejected")
|
||
|
|
}
|
||
|
|
data, err := geojson.MarshalMercuryTransit(info, geojson.PlanetTransitOptions{
|
||
|
|
TimeMarkers: &geojson.TimeMarkerOptions{TimeScale: astro.TimeScaleUT1},
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if collection, _ := decodeTransit(t, data); collection.TimeScale != "UT1" {
|
||
|
|
t.Fatal("UT1 export must carry time_scale")
|
||
|
|
}
|
||
|
|
}
|