Skip to content

Commit 087d927

Browse files
authored
Refactor/home page background (#522)
* feat: assets * fix: background * fix: lang * fix: background * fix: android crash * fix: background * fix: pin maplibre_gl to gzip fix * fix: developer mode * fix: home page weather card * fix: home page weather icon * fix: home page weather background * feat: home sheet next-hour rain trend from rainforecast API Wire the home sheet's 近 1 小時降雨趨勢 card to GET /api/weather/rainforecast/{code} (exptech.dingbot.tw), decoding the start/60×mm envelope into RainHourTrend with loading/error states. * fix: home background blur/dim flicker Keep the blur/dim layer's tree shape constant (ImageFiltered.enabled + an always-present transparent scrim) so the sky's element is never re-parented when the sheet starts climbing or content scrolls — which used to reset WeatherSkyBackground's shader cache and flash the flat fallback colour. * feat: weather phenomenon icons finer than the backdrop modes CWB's ones digit refines the glyph: showers, fog banks, hail and lightning each get their own icon (with 大雷雨 filled, the heaviest rung), while the accent colour keeps following the resolved mode so the family tint stays consistent. * feat: beautify data page Featured earthquake card on primaryContainer plus a two-column ranking grid with accent-coloured icons and badges. * feat: home rain trend summary + minute axis RainHourTrend gains a structured summary (grade, sustained, stop time) that drives a one-line reading under the card title; the chart's axis now labels minutes-from-now ticks instead of Taipei wall clock times. * feat: wind map arrows with baked-in outline, speed-coloured Pre-render one PNG per speed bucket with a black outline baked in (MapLibre's icon-halo only works on true SDF images), pick the image via a step expression, and expose readingIcon so sheets can reuse the arrow. * feat: wind chart segments coloured by speed bucket The speed curve is drawn in per-bucket runs (the same white→pink ramp as the map arrows) instead of one flat line, and the X-axis ticks use speed-coloured WindArrowIcon glyphs; the station hero gains the same rotating arrow icon. * fix: wind chart tooltip crash on segmented bars fl_chart requires one tooltip item per touched spot; a touch at a segment boundary hits two bars at the same x, and deduping them shrank the list — drawTouchTooltip threw "tooltipItems and touchedSpots size should be same". windTooltipItems returns every item; the identical duplicates overlap and read as one card. * feat: 24M tile cache with fill-style warm injection Bump the native memory cache to 24M and warm outward from the selected frame (0 ±4, then ±12 and beyond while space remains): warm() gains a fillUntil target so injection stops at the mirror's cap instead of churning LRU, and the timeline re-centres its warm band when scrubbing past it. * feat: QPESUMS forecast overlay with scan/county/town chrome Extract the radar layer's scan-range + county + township overlay into a ScanRangeOverlayChrome mixin and reuse it for the QPESUMS layer, so the next-1-hour forecast layer draws the same boundaries as the radar. * fix: self-heal map platform view on iOS hot restart After a hot restart the engine can still own the previous isolate's view id, so UiKitView creation fails with recreating_view and onMapCreated never fires. Remount the map with a fresh key when it isn't ready within 4s (bounded retries), and treat the benign platform exception as neither a log entry nor a fatal crash. * fix: compass above all overlays Replace the native MapLibre compass (rendered inside the platform view, so any Flutter overlay — typhoon forecast tooltips, an expanded sheet — paints over it) with a Flutter MapCompass at the top of the map stack, driven by the camera bearing and tapping to reset north. * fix: space the QPESUMS layer label 未來1小時 → 未來 1 小時 for the four Chinese variants. * refactor: extract notification tap routing into its own file Legacy handled tap → navigation inside notify.dart; the rewrite had it as a private method on the app service host. Move it to notification_routes.dart (which already owns the channel → route table) as routeNotificationTap — the app host just registers it on NotificationTaps — with an injectable navigate for tests. * feat: open DPM points in an external map app The DPM detail sheet gains an "open in maps" action that hands the tapped point to Google Maps / Apple Maps, or copies its coordinates. A shared MapLaunchTarget + mapsAppUrl keep the URI schemes in one place, and the platform's home map app is marked 預設 in the picker. * style: reformat the notification route navigator typedef * feat: show rain-trend update time and a no-data pane The section header now carries the data's server `start` stamp as Taipei wall clock, and when no trend data is available the chart band becomes a grey pane (spinner during the first fetch, error + retry on failure) instead of falling back to a fabricated placeholder chart. The chart itself is anchored to the corrected clock: minutes outside the data window render as full-height grey rods, so the covered span reads honestly even when the fetch is stale. * feat: outline lightning markers for visibility over pale tiles The strike dot/cross now bake a black offset ring like the wind arrows do — the icon-colour tint (multiply) leaves the black outline black, so yellow and blue strikes stay readable on the frosted card and pale map tiles. Icons are also ~60% larger to match the wind arrows' on-screen weight, and the legend marks mirror the outline. * refactor: bake lightning colours in so the outline survives tinting A non-SDF PNG shown through MapLibre's `iconColor` gets its RGB *replaced*, which erases the black outline — so instead of one image per shape plus a colour match, pre-render one PNG per shape × age bucket (colour + outline baked) and pick it straight from the feature's `icon` property. No tint, so the ring stays black. * style: thicken the lightning outline to match the wind arrows 4.0 on the 64 px mark is the same proportional ring as 5.5 on the wind arrows' 96 px canvas, so both glyphs read the same after the layer scales them to equal on-screen size. * feat: expose the township-name toggle on every map layer The base map draws township names past townLabelMinZoom; the setting to turn them off used to live on the raster chrome only, so layers without a scan-range menu (wind, RTS, lightning) could not change it. Lift the state into MapScaffold, hand it to every layer's settings menu via buildTopTrailingChrome, and fall back to a standalone label menu for layers that ship no chrome. Menus are also sectioned (reference layers vs map settings) like the typhoon one, with a scroll view so long menus never fall off the bottom of the screen. * fix: settle the compass needle on reset and park it lower A programmatic north reset may not emit a final camera event, so the needle could keep the old bearing after tapping the compass. Set the bearing to zero directly and give the compass breathing room under the layer switcher chip. The rain layer's interval chip now uses MapChipButton so every layer's chrome shares a height and the compass lines up. * feat: use the Flutter MapCompass on the report and replay maps The embedded maps still used the native MapLibre compass, which lives inside the platform view and can be covered by Flutter overlays (the report sheet). Switch both to the Flutter MapCompass, fed by the camera bearing, with the same instant-reset fix as the map tab. * feat: treat a dry rain-hour forecast as no rain An empty series in the rainforecast envelope (or an all-zero one) is the API's way of saying no rain this hour, so decode returns a dry trend instead of failing, the home trend card hides itself, and the chart paints minutes outside the window as one continuous grey band rather than full-height bars. A no-data pane labels the missing span. * feat: grow the hero forecast card on a dry hour When the next-hour forecast is dry there is no rain chart to show, so the hero slot swaps the trend card for the 24-hour forecast card, which starts as a one-glance summary and expands into the full card as the sheet is pulled up — one widget, not two cards. * feat: centre the home backdrop on GPS in current-location mode 所在地 is a place, not a region: frame the camera on the live GPS fix (a small span around it) instead of the whole township, while saved townships keep their bounds-centred framing. Falls back to the township box, or the whole island, when no fix is available. * perf: square the radar scan-range terms by multiplication math.pow(x, 2) routes through the generic power path and needs an `as double` cast; x*x is a plain multiply on the already-double operands. Equivalent up to floating-point rounding. * perf: binary-search the wind chart's nearest sample nearestSample ran a linear scan for every axis tick and tooltip — a 7 d wind series is ~170 points, and the x-values are monotonic, so a binary search finds the same index in O(log n). Ties resolve to the earlier index, matching the old strict-< scan. * refactor: share the wind arrow outline directions The 8 offset neighbours that bake the black outline were declared twice — once in the map PNG renderer, once in WindArrowIcon — with identical values. Hoist one windOutlineDirs constant both use. * refactor: drop the dead string sort in the lightning cache eviction The frame ids were sorted as strings, then immediately re-sorted as ints, wasting a pass and repeatedly int-parsing inside the comparator. Keep the int sort only. * refactor: tidy the tile-cache inject chunk boundary Clamp the chunk end with min() and always pass the same sublist form instead of branching on whether the last chunk is short. * perf: cut scroll-frame cost on the home sheet The draggable sheet re-rendered its whole backdrop every scroll tick: the sky shader repainted each frame even under the blur, the scroll blur re-computed every tick with nothing cached behind it, and card rain kept running on cards already scrolling off-screen. - Freeze the sky (active=false) the instant the list scrolls — the blur about to cover it hides the hold, and holding the last frame lets the blur layer cache; the state clock stops with the ticker, so resuming at the top restarts where it left off. - Quantise the scroll blur/dim to a 4-step ladder, so the full-screen blur re-renders only on level changes instead of every tick. - Rebuild HomeContent's ListView shell only when the sky re-bakes; the scroll-driven reveal/expansion lives on one inner builder, merging the hero's bottom-gap and dry-hour forecast builders. - Stop RainOnGlass and the card-water LiquidFun worlds the moment the position gate closes (cut the water outright instead of draining), and resume on the next rebuild once the gate reopens. * perf: cut the home backdrop's per-frame cost and fix forced-look rain The sky screen pass was a full-screen shader whose output is a pure vertical gradient of a fixed LUT column, so it is now CPU-baked once per re-bake (4×1024) and stretched per frame. The cloud shader's LUT probes share a constant u, so the horizontal filtering moved to a CPU-extracted 1×141 column at bake time and each probe is a vertical 2-tap. The galaxy layer and starmap, never loaded in production, are gone. Base/haze sky colours arrive as baked uniforms, and dead uniforms (iFrustumA/C, iSunElevParam) are dropped. A parity test pins the baked gradient to the reference shader within its own dither. The rain particle field no longer allocates a Color or boxes a clamp num per drop each frame (up to 1792 drops at storm grade); the packed ARGB32 and branch clamps are bit-identical. Forcing an experimental weather mode now drops the live rain/snow/humidity overrides, so a forced clear sky no longer keeps raining. The resolution is a pure function with a regression test. * feat: custom layer order in the map layer picker The picker's header gains a reorder button that opens a fixed-height editor over the sheet (two stacked drag sheets felt broken). The saved order is a list of layer ids resolved against each surface's layers via orderedLayers(), so a layer added after the order was saved appends at the bottom, a stale id is ignored, and duplicates count once — future layers can never throw. Every drop persists; a reset button restores the declared order. * feat: add QPESUMS as a default map layer choice The bottom-nav More tab's default-layer picker was missing the QPESUMS layer. Add it alongside radar, with its own nav label and icon, so users can default the Map tab to the 1-hour precipitation forecast. * feat: group map layers into six ordered categories Replace the broad three-way split (reference / weather / hazard) with the six categories the app's data actually falls into — earthquake, typhoon, weather observations, satellite, radar, and daily life. The layer picker and the default-layer settings page both group under these headers, and `categoryOf` falls back deterministically so a layer added later still resolves without crashing. * feat: let the layer order editor reorder categories too The picker's order sheet previously only reordered layers within a category. Persist the category order alongside the layer order (same tolerant merge, same reset) and make a category header draggable — it moves its whole block, while a layer drag still clamps to its category and a drop always snaps to a group boundary so a block can't land inside another group. * refactor: split the layer order editor into two levels Reordering a flat list of categories and layers mixed together got confusing once categories were sortable — a header drag moved a whole block but a stray drop could still land mid-list. Make the editor two-level instead: level 1 reorders the categories (draggable, persisted to the category order), and tapping a category drills into level 2 to reorder just its layers. Each level is a homogeneous list, so no block banding or boundary snapping is needed, and a single-layer category isn't drill-in-able at all. * feat: reorder default map-layer categories Set the default category order to earthquake → radar → typhoon → weather → satellite → life, moving the radar group up past typhoon. This is the declared order `orderedCategories` falls back to when the user hasn't saved a custom category order. * chore: bump flutter-maplibre-gl to 7c2e249 Advance the pinned maplibre-gl ref from d3deabf to 7c2e249 for the newer native tile-bridge changes. * perf: render weather/cloud shaders at mediump precision Drop highp float to mediump in the weather sky, card-water, rain-on-glass, and sun-flare fragments — mediump is sufficient for their colour work and is cheaper on tile-based GPUs. * refactor: merge the three v2 tile APIs and repositories Radar, satellite, and QPESUMS each had a near-verbatim API class and repository impl differing only in the URL path segment. Collapse them into a parameterised `FrameTileApi` (path + shared delta-frame decode) and a single `FrameTileRepositoryImpl` that implements all three empty domain interfaces; the provider layer still injects each overlay under its own interface. * refactor: share degToRad, rampColor, and drop dead ramp code Centralise the `* pi / 180` conversion (11 sites) into `core/geo/geo_math.dart`, share the value-ramp interpolation `rampColor` between the rain and weather-station layers, and let `LatLng` reuse degToRad instead of a private alias. * refactor: give MapLayer a defaults mixin for unused members Sheet layers (weather stations, rain, lightning, RTS, typhoon, DPM) each hand-wrote the same ten-plus no-op bodies for the MapLayer members they don't use. A `MapLayerDefaults` mixin provides them once; layers override only what they actually do. Also folds the shared 4/maxZoom floor into the defaults. * refactor: share meteor repo decodes, bounds, and calibrated time - Extract the code→station map and delta-seconds history decodes shared by the meteor weather/rain/lightning repositories. - Move boundsFromPoints into camera_fit and reuse it from the report detail page instead of a second min/max loop. - Use AppTime.utc for the rain-trend dry/placeholder "now" instead of DateTime.now, per the calibrated-time convention. * refactor: fold RainMapLayer into a generic station layer Generalize WeatherStationLayer over the snapshot/trend/observation types so the rain overlay (which only differs in its interval-scrubbing source) is a thin subclass instead of a parallel copy. * refactor: generalize the meteor snapshot API and repositories The weather / rain / lightning datasets share the station, latest, list, at, and trend surface; fold the three near-identical clients and impls into one parameterized MeteorSnapshotApi / MeteorSnapshotRepositoryImpl. * refactor: share typhoon payload decoding Three models each decoded {updated, cyclones} payloads and blank/coord helpers by hand; pull the common decodes into typhoon_decode and lat_lng pair helpers. * refactor: route Taipei wall-clock conversions through AppTime.taipei Replace the ad-hoc +8h DateTime arithmetic with one calibrated facade so every wall-clock conversion reads the same way. * refactor: share small inline UI building blocks Add LegendDot, InlineLoading, trimTrailingZero, and an EventType icon key + resolver so the dozen hand-rolled spinners, status dots, axis labels, and event icons render from one place. * refactor: share the map sheets' extent plumbing and chrome Centralize extent listening, the at-top flag, the grip, the frosted surface, and the full draggable-sheet chrome (ExtentSheetChrome) that four sheets used to copy verbatim. * refactor: centralize API path constants and drop the dead v1 report endpoint Put the wire paths shared by the ETag cache and the API clients in one ApiPaths registry so they can't drift, reuse MeteorDecode in the frame list, and delete the unused /api/v1/eq/report client. * feat: more satellite channel * feat: expand satellite channels to all Himawari bands and products Add the full AHI band catalogue (B01–B16) plus the named products the satellite-tiles-go backend serves (btd_*, cloudtop, truecolor, ash, …), each with its own id/label mapping. The frame API now takes a `channel` query; a band can carry a `style` for temperature-scaled renderings. * feat: bright-yellow admin frame over satellite layers Satellite imagery is fully opaque and overall dark, so the white admin frame washes out on it. AdminOutline.add takes an optional lineColor and satellite draws bright yellow country/county + dark yellow township borders (typhoon's satellite underlay too); radar/QPESUMS keep the white core. Add an onReloadActive hook to MapLayer chrome so a style change can re-mount a layer, and stop moving the camera on a bare layer switch. * feat: satellite layer styles, legends and switcher subtitles Thermal bands get a colour-style menu (grayscale/jma/bd) that re-mounts the layer through onReloadActive; every channel's picker tile shows a subtitle with the band or formula. Per-channel legends explain the colour scale and why parts are transparent (clear sky, night, no value), all localised across the supported locales. * fix: degrade 所在地 to nationwide when GPS is unavailable A lost GPS fix left RegionStore on the stale last-known township, so the home backdrop kept framing and outlining a place the user wasn't in. LocationMonitor now clears the code when location becomes unusable, and the backdrop frames on a cached fix (never a live read) so a simulator with no location falls back to the nationwide view instantly instead of waiting out the 10s GPS timeout. * chore: pin maplibre_gl git dependency in pubspec.lock A `pub get` run while the local path override was active had rewritten the lock entry to the machine-local fork path. Restore the committed git source so CI resolves the pinned revision. * fix: clear the stale township frame when 所在地 loses GPS _restackOverlays fell back to the last applied code, so an explicit null (GPS lost, or the nationwide view) re-drew the previous township's mesh and purple selection outline. The caller now owns the subject and a deliberate null reaches the outline layers unchanged, clearing the frame. * fix: weather icon * feat: ECMWF/GFS wind-forecast overlays with particle animation Add the 數值預報 group to the map: a QPESUMS sibling that fetches each model's WND1 grid (/api/v1/wind/<model>/<ts>.bin) and rides a CPU particle field over the wind tiles, ported from the web renderer's trail-buffer pass. A forecast opens on the frame nearest the present instead of its furthest step (nowFrameIndex), the 0.25° grids cap at zoom 7, and the layer draws its own admin frame via the shared AdminOutlineChrome. * feat: 國界 borders default-off on every reference overlay The world-country frame is noise on a Taiwan-focused surface, so it now ships off everywhere and is an explicit toggle: radar/QPESUMS fold their admin chrome onto the shared AdminOutlineChrome (which gains the global toggle and its legend row), and satellite gets the same toggle in both its style menu (thermal bands) and a new reference menu (products and reflectance bands), with the legend row following the switch. * refactor: legend units always shown under the scale Drop the appendUnit mode so every value column stays numbers-only and the unit lives on its own line below the ramp. * feat: cache wind-forecast tiles in the immutable ETag store ECMWF/GFS tile URLs are content-addressed like radar/satellite, but the marker list never covered the wind path — every revisit re-downloaded the frame. One marker brings them into SQLite, the native intercept, and the warm path, with a parity test alongside the radar one. * perf: equivalent simplification of the wind particle hot path Cut the per-frame CPU cost without changing the picture: the view rotation is computed once and inlined instead of a sin/cos per particle, u/v bilinear samples share one cell-geometry pass, the speed buckets are reused instead of allocated every frame, and the layer stops re-casting the repository. * feat: per-hour usage history chart on the Debug page NetworkUsageStore gains a zero-filled per-hour history query, and the Developer page plots the trailing 24 hours under the network section. * feat: colour-coded 現在/歷史/未來 eras on the map timeline Frames either side of the present now carry an era label in a fixed hue (history blue, now green, future purple, light/dark shaded per theme) on both the headline time and the ruler ticks. * refactor: extract Debug network chart into a tested public widget The per-hour usage chart leaves DeveloperPage as a public NetworkUsageChart widget so the stacked downloaded/saved bars and the empty placeholder are covered by widget tests instead of living inline in the page. * style: reformat drifted map timeline and wind files Brings files that had drifted from dart format back in line (pure layout changes; no behaviour touched). * feat: red/green network chart with mixed or single-series toggle Download plots red and Saved green for contrast, and a segmented switch chooses stacked-both, download-only, or saved-only — the legend and tooltip follow the selection. * feat: 24h/7d window toggle on the network usage chart Store history() aggregates into arbitrary bucket widths, so the chart can switch between 24 hourly points and 28 six-hour points for the trailing week. The x-axis is labelled in Taipei wall-clock (11日13時). * fix: space out x-axis labels on the network usage chart fl_chart's BarChart ignores SideTitles.interval (discrete x slots), so every hour was labelled and the full-width 11日13時 text collided. The step is now applied inside getTitlesWidget — every 6 h on the 24h window, every 7 buckets on 7d — and the 7d bars are slimmed so 28 fit the width. * fix: keep the network chart tooltip inside the screen The tooltip for edge bars rendered past the phone bounds. Clamp the bubble with fitInsideHorizontally/Vertically and cap its width so it always stays within the chart. * fix: a future forecast step is never labelled 現在 nowFrameIndex used absolute distance, so at 21:43 a 3-hourly ECMWF set labelled the 23:00 prediction as the present (77 min away) over the already-due 20:00 (103 min). The present is now the newest frame at or before now — observed data is unchanged, and a forecast never presents a prediction as current before it has happened. * feat: surface a snackbar when the chosen map app cannot open Picking Google/Apple Maps now awaits the launch result and, when the app is missing, shows an error snackbar instead of silently doing nothing. The mapAppOpenFailed string is added across every ARB and the l10n generated code is refreshed to match. * feat: map timeline shows a forecast's issue time and valid span A next-hour forecast frame at 21:00 estimates the 21:00-22:00 window, so QPESUMS now renders the selected frame as a range ("21:50 – 22:50") instead of a bare instant read as a point measurement. Wind layers (ECMWF/GFS) additionally name the model-run issue time (資料時間) under the caption: the API serves only the latest run, so the oldest frame is the run start. * refactor: DPM sheet accents per sub-layer, drop category filters Restroom/shelter chips are gone from the peek; each body now takes its sub-layer's colour accent. Map-app flow is folded into one pickAndOpenMapApp helper with a shared failure snackbar, and AED pages can dial the emergency phone via a tel: helper (mapAppCallFailed when no handler). * style: polish typhoon warning and waypoint cards Warning blocks gain an icon and a border; tapped waypoints become filled circle markers with a forecast-lead line and a clearer two-line layout. * fix: restore maplibre_gl git dependency in pubspec.lock A local pubspec_overrides.yaml path fork leaked into the lockfile and failed the check_pubspec_lock.sh CI gate. Regenerated the lock with the override aside so maplibre_gl resolves back to the pinned git ref. * fix: typhoon cards * fix: typhoon cards
1 parent ede52cc commit 087d927

287 files changed

Lines changed: 38409 additions & 4773 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

android/app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ android {
3333
applicationId = "com.exptech.dpip"
3434
minSdk = 26
3535
targetSdk = flutter.targetSdkVersion
36-
versionCode = 300909001
36+
versionCode = 300909009
3737
versionName = flutter.versionName
3838
}
3939

android/app/src/main/kotlin/com/exptech/dpip/MainActivity.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@ import io.flutter.embedding.android.FlutterActivity
44
import io.flutter.embedding.engine.FlutterEngine
55
import io.flutter.plugin.common.EventChannel
66
import io.flutter.plugin.common.MethodChannel
7+
import org.maplibre.android.MapLibre
78

89
/**
910
* Hosts the app's native platform channels. Each capability that replaces a
1011
* third-party plugin is registered here against the engine's binary messenger.
1112
*/
1213
class MainActivity : FlutterActivity() {
14+
override fun onCreate(savedInstanceState: android.os.Bundle?) {
15+
// The maplibre_gl fork's HttpRequestImpl class-init calls
16+
// MapLibre.getApplicationContext(), which throws unless MapLibre was
17+
// initialized first — and it runs during automatic plugin registration
18+
// (inside super.onCreate). Init before the engine attaches or startup
19+
// dies with ExceptionInInitializerError and stalls on the splash.
20+
// getInstance is idempotent; MapSnapshotChannel re-inits later.
21+
MapLibre.getInstance(applicationContext)
22+
super.onCreate(savedInstanceState)
23+
}
24+
1325
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
1426
super.configureFlutterEngine(flutterEngine)
1527
val messenger = flutterEngine.dartExecutor.binaryMessenger

api.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
| `openRtsSse` | `/api/v2/trem/rts?sse=1&compress=1` | `lbApi` | `api.lb-{tpe1,khh1}.exptech.dev` |
2929
| `getRtsRealtime` | `/api/v2/trem/rts` | `lbApi` | `api.lb-{tpe1,khh1}.exptech.dev` |
3030
| `getEewRealtime` | `/api/v2/eq/eew` | `lbApi` | `api.lb-{tpe1,khh1}.exptech.dev` |
31-
| `getLatestReports` | `/api/v1/eq/report` | `coreApi` | `api.core-{tyo1,tnn1}.exptech.dev` |
3231
| `getReportList` | `/api/v2/eq/report` | `coreApi` | `api.core-{tyo1,tnn1}.exptech.dev` |
3332
| `getReport` | `/api/v2/eq/report/{id}` | `coreApi` | `api.core-{tyo1,tnn1}.exptech.dev` |
3433

@@ -63,14 +62,31 @@ max-age=300`)。`{sec}` 就是解出清單後的 10 位數秒,直接使用
6362

6463
### 衛星雲圖(v2)—— `core-tnn1`
6564

66-
Himawari Band-13 IR XYZ WebP。時間清單是差量編碼的 Unix 秒(`[baseSec, Δ, …]`),
67-
在 API 主機上帶 ETag/304;tile 在 **static** 主機。`{sec}` 就是解出清單後的
68-
10 分鐘秒,直接使用。
65+
Himawari-9 AHI 的 XYZ WebP,預設是 Band-13 IR。時間清單是差量編碼的
66+
Unix 秒(`[baseSec, Δ, …]`),在 API 主機上帶 ETag/304;tile 在 **static**
67+
主機。`{sec}` 就是解出清單後的 10 分鐘秒,直接使用。
68+
69+
`?channel=` 選取渲染的頻道或產品 —— 單一頻道用數字(`13`)、命名產品用名稱
70+
`btd_wvirw``cloudtop`…,即 `satellite-tiles-go/docs.md` 的產品目錄)。
71+
帶 channel 時時間清單為該 channel 的交集(產品需要的頻道缺一就不可渲染,
72+
`list` 只列齊全的時刻)。App 的圖層選擇器為每個 channel 註冊一個獨立圖層
73+
`satellite` 保留給 B13,其餘為 `satellite-<channel>`)。
74+
75+
| 方法 | 路徑 | 層級 | 主機 |
76+
|---|---|---|---|
77+
| `getFrames` | `/api/v2/tiles/satellite/list[?channel=…]` | `coreExclusiveApi` | `api.core-tnn1.exptech.dev` |
78+
| `tileUrl` | `/api/v2/tiles/satellite/{sec}/{z}/{x}/{y}.webp[?channel=…]` | `coreStaticExclusive` | `static.core-tnn1.exptech.dev` |
79+
80+
### 未來1小時降水預報 QPESUMS(v2)—— `core-tnn1`
81+
82+
QPESUMS 定量降水預報 XYZ WebP。時間清單是差量編碼的 Unix **毫秒**
83+
`[baseMs, Δ, …]`);tile 在 **static** 主機。`{ms}` 就是解出清單後的 13 位數
84+
毫秒,直接使用(時間軸解析已同時支援秒與毫秒)。
6985

7086
| 方法 | 路徑 | 層級 | 主機 |
7187
|---|---|---|---|
72-
| `getFrames` | `/api/v2/tiles/satellite/list` | `coreExclusiveApi` | `api.core-tnn1.exptech.dev` |
73-
| `tileUrl` | `/api/v2/tiles/satellite/{sec}/{z}/{x}/{y}.webp` | `coreStaticExclusive` | `static.core-tnn1.exptech.dev` |
88+
| `getFrames` | `/api/v2/tiles/qpesums/list` | `coreExclusiveApi` | `api.core-tnn1.exptech.dev` |
89+
| `tileUrl` | `/api/v2/tiles/qpesums/{ms}/{z}/{x}/{y}.webp` | `coreStaticExclusive` | `static.core-tnn1.exptech.dev` |
7490

7591
### 防災地圖 DPM(v2)—— `core-tnn1`
7692

@@ -172,6 +188,7 @@ typhoon)共用同一組形狀:`/api/v5/meteor/{family}` 是最新快照、`/
172188
|---|---|
173189
| `getLocalizationProgress` | `https://exptech.dev/api/v1/dpip/locale` |
174190
| `getReleases` | `https://api.github.com/repos/ExpTechTW/DPIP/releases`(ETag;`per_page=30`|
191+
| `getRainHourForecast` | `https://exptech.dingbot.tw/api/weather/rainforecast/{code}``{code}` = 鄉鎮 3 碼;回應為單 series 信封 `{"<系列名>": [{"start": 秒, "rain": [60 × mm]}]}`;空 series `[]` = 該小時無雨,卡片隱藏) |
175192

176193
## curl 可用性(2026-08-02,HTTP 狀態碼)
177194

assets/weather/clouds/00.webp

88.2 KB
Loading

assets/weather/clouds/01.webp

89.1 KB
Loading

assets/weather/clouds/02.webp

88.8 KB
Loading

assets/weather/clouds/03.webp

75.3 KB
Loading

assets/weather/clouds/04.webp

95.7 KB
Loading

assets/weather/clouds/05.webp

68.8 KB
Loading

assets/weather/clouds/06.webp

60.8 KB
Loading

0 commit comments

Comments
 (0)