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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user