feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具

落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-03 11:37:53 +08:00
co-authored by Cursor
parent 15a9db374a
commit bd22d9dddd
248 changed files with 26309 additions and 842 deletions
+148
View File
@@ -0,0 +1,148 @@
package natal
import (
"strings"
"unicode/utf8"
)
// City is a built-in birth place with WGS84 coordinates.
type City struct {
Name string
Lat float64
Lng float64
}
// Cities lists common CN cities for MVP geocode (no network).
var Cities = []City{
{"北京", 39.9042, 116.4074},
{"上海", 31.2304, 121.4737},
{"广州", 23.1291, 113.2644},
{"深圳", 22.5431, 114.0579},
{"杭州", 30.2741, 120.1551},
{"成都", 30.5728, 104.0668},
{"重庆", 29.5630, 106.5516},
{"武汉", 30.5928, 114.3055},
{"西安", 34.3416, 108.9398},
{"南京", 32.0603, 118.7969},
{"天津", 39.3434, 117.3616},
{"苏州", 31.2989, 120.5853},
{"长沙", 28.2282, 112.9388},
{"郑州", 34.7466, 113.6254},
{"青岛", 36.0671, 120.3826},
{"大连", 38.9140, 121.6147},
{"厦门", 24.4798, 118.0894},
{"福州", 26.0745, 119.2965},
{"昆明", 25.0389, 102.7183},
{"贵阳", 26.6470, 106.6302},
{"南宁", 22.8170, 108.3665},
{"海口", 20.0440, 110.1999},
{"哈尔滨", 45.8038, 126.5349},
{"长春", 43.8171, 125.3235},
{"沈阳", 41.8057, 123.4315},
{"石家庄", 38.0428, 114.5149},
{"太原", 37.8706, 112.5489},
{"济南", 36.6512, 117.1201},
{"合肥", 31.8206, 117.2272},
{"南昌", 28.6820, 115.8579},
{"兰州", 36.0611, 103.8343},
{"乌鲁木齐", 43.8256, 87.6168},
{"拉萨", 29.6520, 91.1721},
{"呼和浩特", 40.8414, 111.7519},
{"银川", 38.4872, 106.2309},
{"西宁", 36.6171, 101.7782},
{"香港", 22.3193, 114.1694},
{"澳门", 22.1987, 113.5439},
{"台北", 25.0330, 121.5654},
{"宁波", 29.8683, 121.5440},
{"无锡", 31.4912, 120.3119},
{"佛山", 23.0215, 113.1214},
{"东莞", 23.0207, 113.7518},
{"温州", 27.9943, 120.6994},
{"泉州", 24.8741, 118.6759},
{"珠海", 22.2710, 113.5767},
}
// DefaultCoords is used when place is missing (东八区中部近似).
const DefaultLat = 30.0
const DefaultLng = 114.0
// ResolvePlace returns lat/lng and whether a named city matched.
// Accepts plain city ("杭州") or 省市区 ("浙江省 杭州市 西湖区" / "北京市 市辖区 朝阳区").
func ResolvePlace(place string) (lat, lng float64, matched string, ok bool) {
place = strings.TrimSpace(place)
if place == "" {
return DefaultLat, DefaultLng, "", false
}
// exact city table hit
for _, c := range Cities {
if c.Name == place {
return c.Lat, c.Lng, c.Name, true
}
}
tokens := splitPlace(place)
// prefer matching city-level tokens (usually 2nd), then others
order := append([]string{}, tokens...)
if len(tokens) >= 2 {
order = append([]string{tokens[1]}, tokens[0])
if len(tokens) >= 3 {
order = append(order, tokens[2:]...)
}
}
for _, tok := range order {
if tok == "" || tok == "市辖区" || tok == "县" {
continue
}
key := normalizeAdmin(tok)
for _, c := range Cities {
if c.Name == key || strings.Contains(tok, c.Name) || strings.Contains(c.Name, key) {
return c.Lat, c.Lng, c.Name, true
}
}
}
// province-only fallback: use first token city if 直辖市
if len(tokens) > 0 {
key := normalizeAdmin(tokens[0])
for _, c := range Cities {
if c.Name == key {
return c.Lat, c.Lng, c.Name, true
}
}
}
return DefaultLat, DefaultLng, "", false
}
func splitPlace(s string) []string {
s = strings.ReplaceAll(s, "/", " ")
s = strings.ReplaceAll(s, "", " ")
s = strings.ReplaceAll(s, "", " ")
s = strings.ReplaceAll(s, ",", " ")
parts := strings.Fields(s)
out := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
out = append(out, p)
}
}
return out
}
func normalizeAdmin(s string) string {
s = strings.TrimSpace(s)
suffixes := []string{"特别行政区", "维吾尔自治区", "壮族自治区", "回族自治区", "自治区", "省", "市", "地区", "盟"}
for _, suf := range suffixes {
if strings.HasSuffix(s, suf) && utf8.RuneCountInString(s) > utf8.RuneCountInString(suf) {
return strings.TrimSuffix(s, suf)
}
}
return s
}
// CityNames returns names for API/UI pickers.
func CityNames() []string {
out := make([]string, len(Cities))
for i, c := range Cities {
out[i] = c.Name
}
return out
}