crs-l2-fiji-antimeridian

Status: done
Score: 0.000
Duration: 3:39
Cost: 0.70¢
Model: deepseek/deepseek-v4-flash

Map (reference ← swipe → agent)

0:00
I'm pulling together per-transect coverage figures for our survey report, but some of the lines in fiji_transects_wgs84.geojson render as arcs spanning the entire globe instead of short local segments. Can you fix the geometries so they render correctly, convert them to Fiji's national metric grid, and give me each transect as one MultiLineString feature with a length_m attribute in metres? Every feature must come out as a MultiLineString, so do not leave any transects as plain LineString, and the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path. Carry the survey attributes (vessel and survey_date) through unchanged, use transect_id as the key field, and write the result as GeoJSON named fiji_transects_fmg.geojson.
0:00
System info
{
  "adapter": "openrouter",
  "model": "deepseek/deepseek-v4-flash",
  "base_url": "https://openrouter.ai/api/v1"
}
0:02
Let me start by reading the input file to understand its structure and identify the problematic geometries.
0:02
Read /work/fiji_transects_wgs84.geojson
     1	{
     2	"type": "FeatureCollection",
     3	"name": "fiji_transects_wgs84",
     4	"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
     5	"features": [
     6	{ "type": "Feature", "properties": { "transect_id": "T001", "vessel": "Taveuni II", "survey_date": "2025-08-15", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.941879580782086, -17.632301816020771 ], [ 179.456655009654696, -17.623768593111961 ], [ 179.971430438527307, -17.611072392823225 ], [ -179.513794132600054, -17.596198176111951 ], [ -178.999018703727444, -17.579632069513806 ], [ -178.484243274854833, -17.577321872344196 ] ] } },
     7	{ "type": "Feature", "properties": { "transect_id": "T002", "vessel": "Bligh", "survey_date": "2025-08-12", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 179.070288824800542, -17.334767984150709 ], [ 179.725942881740252, -17.171343988196945 ], [ -179.618403061320009, -17.007726843860763 ], [ -178.962749004380299, -16.834850408534852 ] ] } },
     8	{ "type": "Feature", "properties": { "transect_id": "T003", "vessel": "Lomaiviti", "survey_date": "2025-08-12", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.636947428758333, -17.358837040696127 ], [ 178.462815605623859, -17.416074837434927 ], [ 179.288683782489386, -17.471713663377283 ], [ -179.885448040645059, -17.527675804260884 ], [ -179.059579863779533, -17.590577543375694 ] ] } },
     9	{ "type": "Feature", "properties": { "transect_id": "T004", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.873696381462679, -17.803295238757038 ], [ 179.542254331047587, -17.598477321703708 ], [ -179.789187719367533, -17.405549472537043 ], [ -179.120629769782624, -17.19411411231702 ], [ -178.452071820197745, -16.992332922174427 ], [ -177.783513870612836, -16.795171196845544 ] ] } },
    10	{ "type": "Feature", "properties": { "transect_id": "T005", "vessel": "Bligh", "survey_date": "2025-08-19", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.596924830927293, -17.923255155112717 ], [ 178.160696712947697, -18.010328270600898 ], [ 178.724468594968101, -18.099224972140938 ], [ 179.288240476988506, -18.17532145144995 ], [ 179.85201235900891, -18.270380145331412 ], [ -179.584215758970686, -18.358875431719703 ], [ -179.020443876950281, -18.437390943572723 ] ] } },
    11	{ "type": "Feature", "properties": { "transect_id": "T006", "vessel": "Cakaulevu", "survey_date": "2025-08-13", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.622166647099931, -18.382052393687179 ], [ 179.205668025282989, -18.184610858038685 ], [ 179.789169403466019, -17.991866703295319 ], [ -179.627329218350923, -17.789532271264445 ], [ -179.043827840167864, -17.595682751178369 ], [ -178.460326461984835, -17.393483000040479 ], [ -177.876825083801776, -17.203490564162109 ] ] } },
    12	{ "type": "Feature", "properties": { "transect_id": "T007", "vessel": "Vanua I", "survey_date": "2025-08-19", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.920101619592742, -17.14979850360189 ], [ 178.697177550619955, -17.100522188212071 ], [ 179.474253481647168, -17.053867486482115 ], [ -179.74867058732562, -17.004197778897627 ], [ -178.971594656298407, -16.959481244370728 ], [ -178.194518725271195, -16.911697858015476 ] ] } },
    13	{ "type": "Feature", "properties": { "transect_id": "T008", "vessel": "Taveuni II", "survey_date": "2025-08-12", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 179.401134548056262, -17.934415957637167 ], [ 179.67454249485786, -17.899654147497863 ], [ 179.947950441659486, -17.858130727687527 ], [ -179.778641611538916, -17.811143619533269 ], [ -179.50523366473729, -17.768768396969666 ], [ -179.231825717935692, -17.724708363613253 ] ] } },
    14	{ "type": "Feature", "properties": { "transect_id": "T009", "vessel": "Vanua I", "survey_date": "2025-08-13", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 178.068433112639013, -16.657838811131668 ], [ 178.561564721122721, -16.689731320158675 ], [ 179.054696329606429, -16.72173241552732 ], [ 179.547827938090137, -16.748891897767297 ], [ -179.959040453426155, -16.775241845025921 ], [ -179.465908844942447, -16.799281899607834 ] ] } },
    15	{ "type": "Feature", "properties": { "transect_id": "T010", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": true }, "geometry": { "type": "LineString", "coordinates": [ [ 177.602847396853463, -17.277026295424367 ], [ 179.128053569323441, -17.468285296635063 ], [ -179.346740258206609, -17.65419136085664 ], [ -177.82153408573663, -17.837325774502055 ] ] } },
    16	{ "type": "Feature", "properties": { "transect_id": "T011", "vessel": "Taveuni II", "survey_date": "2025-08-20", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.863797870333627, -17.7932433540029 ], [ -176.985724572324699, -17.81273654528005 ], [ -177.107651274315742, -17.824236921378098 ], [ -177.229577976306814, -17.835861250474697 ], [ -177.351504678297886, -17.857559546831705 ], [ -177.473431380288929, -17.871769203842408 ], [ -177.595358082280001, -17.890225470404214 ] ] } },
    17	{ "type": "Feature", "properties": { "transect_id": "T012", "vessel": "Taveuni II", "survey_date": "2025-08-20", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.312195951017685, -17.976122209608448 ], [ 176.362236324041447, -17.722210270969462 ], [ 176.412276697065209, -17.470306839752467 ], [ 176.462317070088972, -17.226475769084448 ] ] } },
    18	{ "type": "Feature", "properties": { "transect_id": "T013", "vessel": "Bligh", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.70823848972006, -17.696306145080879 ], [ -177.205026853648661, -17.960082567744127 ], [ -177.701815217577291, -18.210737758337459 ], [ -178.198603581505893, -18.469124252379427 ] ] } },
    19	{ "type": "Feature", "properties": { "transect_id": "T014", "vessel": "Lomaiviti", "survey_date": "2025-08-13", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.538496893308832, -16.594358164012139 ], [ 176.861572490827996, -16.71494251806207 ], [ 177.184648088347188, -16.832251271240672 ], [ 177.507723685866381, -16.976116506165042 ], [ 177.830799283385545, -17.090939089364483 ] ] } },
    20	{ "type": "Feature", "properties": { "transect_id": "T015", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.278377449219306, -16.724372731501912 ], [ -177.470251917030794, -16.761535677586746 ], [ -177.662126384842253, -16.787182414326686 ], [ -177.854000852653712, -16.818799270133983 ], [ -178.0458753204652, -16.856282470705128 ], [ -178.237749788276659, -16.891308864567598 ], [ -178.429624256088147, -16.914729063096839 ] ] } },
    21	{ "type": "Feature", "properties": { "transect_id": "T016", "vessel": "Taveuni II", "survey_date": "2025-08-15", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 178.445325115686984, -18.225562645051426 ], [ 178.570919872630384, -18.22187730118792 ], [ 178.696514629573755, -18.228085980840198 ], [ 178.822109386517127, -18.243164774607269 ], [ 178.947704143460527, -18.232257588046291 ] ] } },
    22	{ "type": "Feature", "properties": { "transect_id": "T017", "vessel": "Taveuni II", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.949497668404376, -16.593233226112279 ], [ -177.073130399044231, -16.796136633276152 ], [ -177.196763129684086, -16.985064764093782 ], [ -177.320395860323913, -17.188172387851619 ], [ -177.444028590963768, -17.380278567766066 ], [ -177.567661321603623, -17.571298263401527 ], [ -177.691294052243478, -17.76827441812642 ] ] } },
    23	{ "type": "Feature", "properties": { "transect_id": "T018", "vessel": "Bligh", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.111138114901024, -16.851117503263293 ], [ 176.291399614433374, -17.034309766495799 ], [ 176.471661113965695, -17.221302006070296 ], [ 176.651922613498016, -17.402317112686557 ], [ 176.832184113030365, -17.592994204367653 ] ] } },
    24	{ "type": "Feature", "properties": { "transect_id": "T019", "vessel": "Lomaiviti", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 178.007516628261584, -17.706954766350957 ], [ 177.596962177324428, -17.607161226082525 ], [ 177.186407726387301, -17.511279680346377 ], [ 176.775853275450146, -17.418568907300433 ], [ 176.36529882451299, -17.326891370330944 ] ] } },
    25	{ "type": "Feature", "properties": { "transect_id": "T020", "vessel": "Taveuni II", "survey_date": "2025-08-13", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.869640838143511, -17.085093978662378 ], [ 177.34023164697706, -17.269542693344249 ], [ 177.810822455810609, -17.442447410976666 ], [ 178.281413264644158, -17.624621823987695 ], [ 178.752004073477707, -17.807162082048386 ] ] } },
    26	{ "type": "Feature", "properties": { "transect_id": "T021", "vessel": "Vanua I", "survey_date": "2025-08-16", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.258661225850091, -17.092877710432774 ], [ 176.830345357965598, -17.521807453385343 ], [ 177.402029490081105, -17.943438299004075 ], [ 177.973713622196613, -18.366984215963161 ] ] } },
    27	{ "type": "Feature", "properties": { "transect_id": "T022", "vessel": "Vanua I", "survey_date": "2025-08-15", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 177.115699442929383, -17.820293640195572 ], [ 177.180354072206086, -17.755581112632651 ], [ 177.245008701482789, -17.684692899482695 ], [ 177.30966333075952, -17.61995755685173 ], [ 177.374317960036223, -17.544174647257226 ], [ 177.438972589312925, -17.479955835030527 ] ] } },
    28	{ "type": "Feature", "properties": { "transect_id": "T023", "vessel": "Lomaiviti", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.191351883954098, -17.679938619650844 ], [ -177.334313691261116, -17.740394590021616 ], [ -177.477275498568133, -17.794367957957562 ], [ -177.62023730587515, -17.835119945356809 ], [ -177.763199113182168, -17.904282380225705 ], [ -177.906160920489185, -17.955635131096855 ] ] } },
    29	{ "type": "Feature", "properties": { "transect_id": "T024", "vessel": "Vanua I", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -179.108274205314615, -16.84950325482971 ], [ -178.651703763804733, -16.962456436762313 ], [ -178.195133322294822, -17.077410225872036 ], [ -177.73856288078494, -17.187966865781235 ], [ -177.281992439275029, -17.288042922581088 ], [ -176.825421997765147, -17.40904519070677 ] ] } },
    30	{ "type": "Feature", "properties": { "transect_id": "T025", "vessel": "Taveuni II", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 176.574749815324452, -18.225329195672629 ], [ 176.575711406506713, -17.903825702399828 ], [ 176.576672997688945, -17.572376190438085 ], [ 176.577634588871206, -17.245872893430239 ] ] } },
    31	{ "type": "Feature", "properties": { "transect_id": "T026", "vessel": "Cakaulevu", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ 178.630248956242411, -17.547785158041279 ], [ 178.44215451378426, -17.570057172060459 ], [ 178.254060071326109, -17.598148620651486 ], [ 178.065965628867986, -17.611819625668215 ], [ 177.877871186409834, -17.645164465599024 ], [ 177.689776743951683, -17.659546383089758 ] ] } },
    32	{ "type": "Feature", "properties": { "transect_id": "T027", "vessel": "Vanua I", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.773526218774975, -17.183972526301677 ], [ -177.993798674497214, -17.290101550005915 ], [ -178.214071130219452, -17.383420029016879 ], [ -178.434343585941718, -17.487055860389813 ], [ -178.654616041663957, -17.584325384194859 ], [ -178.874888497386195, -17.694189986558115 ] ] } },
    33	{ "type": "Feature", "properties": { "transect_id": "T028", "vessel": "Vanua I", "survey_date": "2025-08-12", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -178.154921880223981, -16.519500569812283 ], [ -177.760867938660567, -16.61473611015824 ], [ -177.366813997097154, -16.706948361526067 ], [ -176.972760055533769, -16.779276042892469 ], [ -176.578706113970355, -16.874986722588798 ] ] } },
    34	{ "type": "Feature", "properties": { "transect_id": "T029", "vessel": "Lomaiviti", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -176.563841265411412, -18.091246216590591 ], [ -176.943316553982299, -17.796168551154441 ], [ -177.322791842553215, -17.510273970538908 ], [ -177.702267131124103, -17.231705179840109 ], [ -178.08174241969499, -16.949176694851047 ] ] } },
    35	{ "type": "Feature", "properties": { "transect_id": "T030", "vessel": "Cakaulevu", "survey_date": "2025-08-19", "crosses_antimeridian_flag": false }, "geometry": { "type": "LineString", "coordinates": [ [ -177.772700226865993, -16.898671712097919 ], [ -177.981739053528088, -17.042373280579991 ], [ -178.190777880190183, -17.175821795383495 ], [ -178.399816706852278, -17.32978967298839 ], [ -178.608855533514344, -17.474383681681747 ], [ -178.817894360176439, -17.613581860280195 ], [ -179.026933186838534, -17.755873350663471 ] ] } }
    36	]
    37	}
0:14
Let me analyze this carefully.

The problem: some transects cross the antimeridian (180° longitude). When coordinates go from positive longitudes (e.g., 179.9) past 180° to negative longitudes (e.g., -179.9), naive rendering interprets this as wrapping all the way around the globe instead of crossing the dateline.

For example, T001 has coordinates:
- 178.94, -17.63
- 179.46, -17.62
- 179.97, -17.61
- -179.51, -17.60
- -179.00, -17.58
- -178.48, -17.58

This is crossing the antimeridian. The fix is to split the line at the antimeridian, adding 360 to negative longitudes to make them continuous (so they become ~180.49, ~181.0, ~181.52, etc.) and then possibly splitting them into separate segments.

Actually, let me think about this more carefully. The approach is:

1. For each line that crosses the antimeridian, split it into segments on each side of the antimeridian.
2. Shift the longitudes on the "west" (negative) side by +360 to make them continuous.
3. Then reproject to Fiji's national metric grid.
4. Calculate length in meters.

Let me think about what projection to use. Fiji's national metric grid... Fiji uses a variety of projections. The most common one for Fiji is the Fiji Map Grid (FMG) which is based on UTM zones. Actually, Fiji spans UTM zones 1, 60, and 59. 

Let me search for the Fiji national grid CRS.

Actually, the "Fiji Map Grid" (FMG) is typically EPSG:3460 (Fiji 1956 / Fiji Map Grid) or maybe something more modern like EPSG:2135 (Fiji 1956 / UTM zone 60S) or EPSG:2136 (Fiji 1956 / UTM zone 1S).

Wait, let me think about this differently. The Fiji Map Grid is actually EPSG:3460 - "Fiji 1956 / Fiji Map Grid". Let me check.

Actually, I recall that "Fiji Map Grid" is a local transverse mercator projection for Fiji. EPSG:3460 is "Fiji 1956 / Fiji Map Grid". But there's also a newer one. Let me just use EPSG:3460 or find the right one.

Let me first write a Python script to:

1. Read the GeoJSON
2. For each feature, check if it crosses the antimeridian
3. If it does, split it at the antimeridian into multiple parts
4. Convert to the Fiji metric grid
5. Calculate length in meters
6. Write as MultiLineString with length_m attribute

Let me check what CRS to use for Fiji Map Grid.

Actually, I know that:
- EPSG:3460 = Fiji 1956 / Fiji Map Grid
- EPSG:3461 = Fiji 1986 / Fiji Map Grid
- EPSG:2135 = Fiji 1956 / UTM zone 60S
- EPSG:2136 = Fiji 1956 / UTM zone 1S

The user said "Fiji's national metric grid" which is likely "Fiji Map Grid" - EPSG:3460 or 3461.

Let me check if pyproj knows about these.
0:14
0:14
Bash python3 -c "
python3 -c "
from pyproj import CRS
# Fiji Map Grid options
for epsg in [3460, 3461, 2135, 2136]:
    try:
        crs = CRS.from_epsg(epsg)
        print(f'EPSG:{epsg}: {crs.name}')
    except:
        print(f'EPSG:{epsg}: not available')
"
EPSG:3460: Fiji 1986 / Fiji Map Grid
EPSG:3461: Dabola 1981 / UTM zone 28N
EPSG:2135: NZGD2000 / UTM zone 60S
EPSG:2136: Accra / Ghana National Grid
1:04
The Fiji Map Grid is EPSG:3460. Let me verify its properties.

Now I need to write a script to:
1. Read the GeoJSON file
2. For features that cross the antimeridian (those with crosses_antimeridian_flag: true), split the LineString at the antimeridian (180° longitude). The issue is that coordinates go from positive longitudes to negative longitudes crossing the date line - this creates lines that wrap around the entire globe.
3. Convert to EPSG:3460 (Fiji Map Grid)
4. Ensure each feature is a MultiLineString
5. Calculate length in meters
6. Write as GeoJSON

Let me think about the antimeridian crossing logic:

For the antimeridian-crossing transects, the coordinates go from east of 180 to west of 180 (e.g., 178.9, 179.5, 179.97, then -179.5, -179.0, -178.5).

When you connect these as a single LineString, it draws across the whole globe instead of wrapping around. We need to split at the antimeridian.

The approach:
1. Find where the longitude jumps from near +180 to near -180 (or vice versa)
2. Split the LineString at that point by inserting a vertex at the antimeridian
3. Create a MultiLineString with two parts (or more if multiple crossings)

For features that don't cross the antimeridian, we just need to ensure they are MultiLineString.

Let me also check if there are features that cross the antimeridian but the coordinates are already adjusted... Looking at the data:

T001: [178.94, 179.46, 179.97, -179.51, -179.0, -178.48] - crosses antimeridian
T002: [179.07, 179.73, -179.62, -178.96] - crosses
T003: [177.64, 178.46, 179.29, -179.89, -179.06] - crosses
...etc.

The issue: a LineString connecting 179.97 to -179.51 will go the long way around (through 180 degrees of longitude the wrong way) instead of the short way across the antimeridian.

To fix: we need to split the line at the antimeridian. The general approach:

1. For each segment [lon1,lon2] where one is east and the other is west of 180:
   - Calculate the intersection point with the antimeridian (180° longitude)
   - Split the segment into two parts
2. Group the resulting segments into two (or more) lines

Let me write a robust script.

Actually, let me think more carefully. The standard approach for antimeridian crossing:

A line from (179, -17) to (-179, -17) should be interpreted as crossing the antimeridian at (180, -17) or (-180, -17). The rendered line should go from (179, -17) to (180, -17) to (-179, -17) OR equivalently from (179, -17) to (-180, -17) to (-179, -17).

The best approach: normalize coordinates to a continuous range and split.

Let me write the script:

```python
import geopandas as gpd
import json
from shapely.geometry import LineString, MultiLineString, Point, box
from shapely.ops import split, linemerge
from pyproj import Transformer
import numpy as np

# Read the GeoJSON
with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

# Fiji Map Grid CRS
crs_wgs84 = "EPSG:4326"
crs_fmg = "EPSG:3460"
transformer = Transformer.from_crs(crs_wgs84, crs_fmg, always_xy=True)

def split_at_antimeridian(coords):
    """Split a LineString that crosses the antimeridian into a MultiLineString.
    Coords is a list of [lon, lat] pairs."""
    
    # First, identify segments that cross the antimeridian
    # We'll work with the coordinates as given
    segments = []
    current_segment = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Check if this segment crosses the antimeridian
        # It crosses if one is east and the other is west of 180, and they're far apart
        # A simpler check: if the longitude difference is more than 180 in absolute value
        # when comparing in a normalized way
        diff = abs(lon2 - lon1)
        
        if diff > 180:
            # This segment crosses the antimeridian
            # The line goes from lon1 to lon2 crossing the dateline
            # lon1 is positive (east), lon2 is negative (west) or vice versa
            
            # Calculate fraction where it crosses
            # We need to interpolate the latitude at the antimeridian
            # The antimeridian is at 180° (or -180°)
            
            if lon1 > 0:
                # Crossing from east to west: lon1 -> 180 -> -180 -> lon2
                # Actually, since lon2 is negative (e.g., -179), the crossing at 180° is:
                # We go from lon1 to 180 (or -180)
                # The distance from lon1 to 180: (180 - lon1)
                # The distance from 180 to lon2: (lon2 + 180) ... wait, lon2 is negative
                # Better: total distance along longitude: (180 - lon1) + (180 + lon2) = 360 - lon1 + lon2
                # Since lon2 is negative (e.g., -179), 180 + lon2 = 1
                
                # Actually, let me think of it differently.
                # From lon1 (e.g., 179.97) to lon2 (e.g., -179.51):
                # Shortest path crosses the 180 meridian.
                # Going east: 179.97 -> 180 (diff=0.03), then 180 -> -179.51 (diff=0.49 in the other direction)
                # Actually, from 180 to -179.51 is a difference of 0.49 going eastward.
                
                # Let's use: total_diff_degrees = (180 - lon1) + (lon2 + 180) = 360 - lon1 + lon2
                # Since lon2 is negative, say -179.51: lon2 + 180 = 0.49
                # So total = 0.03 + 0.49 = 0.52
                
                dist_to_180 = 180 - lon1
                dist_from_180_to_lon2 = lon2 + 180  # if lon2 = -179.51, this is 0.49
                total_dist = dist_to_180 + dist_from_180_to_lon2
                
                # Fraction along the segment to reach the antimeridian
                frac = dist_to_180 / total_dist
                
                # Interpolate latitude
                lat_at_180 = lat1 + frac * (lat2 - lat1)
                
                # Point on antimeridian
                antimeridian_point = [180.0, lat_at_180]
                
                # Finish current segment at antimeridian
                current_segment.append(antimeridian_point)
                segments.append(LineString(current_segment))
                
                # Start new segment from antimeridian
                current_segment = [antimeridian_point, coords[i]]
            else:
                # lon1 is negative, lon2 is positive (crossing from west to east)
                dist_to_180 = 180 + lon1  # e.g., lon1=-179, gives 1
                dist_from_180_to_lon2 = 180 - lon2  # e.g., lon2=179, gives 1
                total_dist = dist_to_180 + dist_from_180_to_lon2
                
                frac = dist_to_180 / total_dist
                lat_at_180 = lat1 + frac * (lat2 - lat1)
                
                antimeridian_point = [180.0, lat_at_180]
                
                current_segment.append(antimeridian_point)
                segments.append(LineString(current_segment))
                
                current_segment = [antimeridian_point, coords[i]]
        else:
            current_segment.append(coords[i])
    
    # Add the last segment
    if len(current_segment) > 1:
        segments.append(LineString(current_segment))
    
    if len(segments) == 1:
        return segments[0]  # Single line, no crossing
    else:
        return MultiLineString(segments)
```

Wait, I need to think more carefully. Let me reconsider the approach.

Actually, for features that cross the antimeridian, the coordinates already represent the correct path - they go from east longitudes across 180 to west longitudes. The problem is that when stored as one LineString, the rendering engine draws the long way around.

The solution is to split the LineString at the antimeridian into a MultiLineString with two parts: one east of 180 and one west of 180.

Let me reconsider the splitting logic more carefully.

For T001: coords = [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

The jump is between [179.97, -17.61] and [-179.51, -17.60]. These two points are very close in reality (only about 0.52 degrees apart across the dateline), but the longitude difference in our coordinate system is 359.48 degrees.

So we need to split here. The first part runs from [178.94, -17.63] to [180.0, interpolated_lat] and the second from [-180.0, interpolated_lat] to [-178.48, -17.58].

Wait, actually the second part should go from [180.0, interpolated_lat] ... no. Let me think about this more carefully.

Going from (179.97, -17.61) to (-179.51, -17.60), the shortest path crosses the 180° meridian. The interpolated point at 180° can be found by linear interpolation.

Since we go from lon=179.97 to lon=-179.51 (which is the same as lon=180.49 going east), the difference is:
- Going east from 179.97: we need to go 0.03° to reach 180°, then from 180° we go 0.49° more east to reach -179.51 (= 180.49)
- Total eastward distance: 0.03 + 0.49 = 0.52°

So the fraction to reach 180° is 0.03/0.52 = 0.0577

The interpolated lat at 180° would be: -17.61 + 0.0577 * (-17.60 - (-17.61)) = -17.61 + 0.0577 * 0.01 = -17.6094

So the first part: [(178.94, -17.63), (179.46, -17.62), (179.97, -17.61), (180.0, -17.6094)]
The second part: [(-180.0, -17.6094), (-179.51, -17.60), (-179.00, -17.58), (-178.48, -17.58)]

Or equivalently: [(180.0, -17.6094), (-179.51, -17.60), ...]

Let me write cleaner code.

Actually, I'll use a slightly different approach. Let me use shapely to help.

Actually, the cleanest approach is:

1. For each coordinate, if the longitude is > 180... no, they're all within [-180, 180].

Let me use the approach of shifting the coordinates. For points east of the dateline, keep as is. For points west of the dateline, add 360 to make them continuous.

Wait, that changes the coordinate values. I need to think about this differently.

The GeoJSON is in WGS84 with longitudes in [-180, 180]. For antimeridian crossing lines, the coordinates correctly represent the path - it's just that the line string "wraps" around the wrong way.

Here's my refined approach:

For each transect with crossing flag:
1. Find the segment where the longitude jumps by > 180° (from positive to negative or vice versa)
2. Interpolate the intersection point at the antimeridian (lon=180°)
3. Split the line into two parts at that point

Let me code this properly.

```python
import json
from shapely.geometry import LineString, MultiLineString
from pyproj import Transformer
import numpy as np

with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

crs_wgs84 = "EPSG:4326"
crs_fmg = "EPSG:3460"
transformer = Transformer.from_crs(crs_wgs84, crs_fmg, always_xy=True)

def split_at_antimeridian(line_coords):
    """Split coordinates that cross the antimeridian.
    Returns a list of lists of coordinates (one per part)."""
    
    parts = []
    current_part = [list(line_coords[0])]
    
    for i in range(1, len(line_coords)):
        lon1, lat1 = line_coords[i-1]
        lon2, lat2 = line_coords[i]
        
        # Detect crossing: longitudes of opposite sign and far apart
        # More precisely: if the difference in longitude when going the short way is > 180
        # The shortest path between lon1 and lon2 is: min(abs(lon2-lon1), 360-abs(lon2-lon1))
        # If 360-abs(lon2-lon1) < abs(lon2-lon1), the short path crosses the antimeridian
        
        diff = abs(lon2 - lon1)
        if diff > 180:
            # Crosses the antimeridian
            # Determine direction
            if lon1 < 0 and lon2 > 0:
                # Crossing from west to east
                # distance from lon1 to -180 (i.e., going west from lon1 to -180)
                # lon1 is negative, say -179.5
                # distance to -180: -180 - lon1 = -180 - (-179.5) = -0.5... 
                # Actually: from -179.5 going west to -180 is 0.5 degrees
                dist_to_180 = -180 - lon1  # this is negative... let me recalculate
                # From lon1 (negative, e.g., -179.5) to -180 going west: 
                # The longitude decreases from -179.5 to -180, difference = 0.5
                # |lon1 - (-180)| = |lon1 + 180| = -lon1 - 180 if lon1 < -180... 
                # Actually if lon1 = -179.5, then -lon1 - 180 = -(-179.5) - 180 = 179.5 - 180 = -0.5
                # Hmm, let me just think physically.
                pass
            
            # Let me think about it differently
            # The segment from (lon1, lat1) to (lon2, lat2) crosses the antimeridian
            # at (180, lat) if going east, or (-180, lat) if going west
            
            # If lon1 > 0 and lon2 < 0: going eastward from lon1 to 180 to lon2
            # Distance from lon1 to 180 (going east): 180 - lon1
            # Distance from 180 to lon2 (going east, through -180): lon2 + 180 (since lon2 is negative, this is positive)
            # Wait, from 180 going east to -179: 180 -> 181 -> ... -> 359 -> -179... 
            # Actually from 180 to -179 going east: we go from 180 to 181 (= -179), so distance = 1
            # The formula: from 180 to lon2 going east: if lon2 < 0, then... 180 + (180 + lon2)? No.
            # 
            # Let me use a different approach. Convert to continuous coordinates.
            # For a point crossing from east to west:
            #   The path is from lon1 (east, say 179) eastward to 180, then continues eastward...
            #   Actually, after passing 180 going east, we're at longitudes > 180. 
            #   -179.5 in continuous eastward coords = 180.5
            #   So from 179 to 180.5, distance = 1.5
            #
            # I'll convert all longitudes to a continuous system and interpolate.
            
            # For the first point (lon1 which is > 0), it stays as is.
            # For the second point (lon2 which is < 0), add 360.
            lon2_cont = lon2 + 360
            
            # Now interpolate: find where lon = 180
            frac = (180 - lon1) / (lon2_cont - lon1)
            lat_at_180 = lat1 + frac * (lat2 - lat1)
            
            # Point at antimeridian
            am_point = [180.0, lat_at_180]
            
            # Finish current part
            current_part.append(am_point)
            
            # If we have at least 2 points, add to parts
            if len(current_part) > 1:
                parts.append(current_part)
            
            # Start new part with antimeridian point (use -180 for the western side)
            am_point_west = [-180.0, lat_at_180]
            current_part = [am_point_west, list(line_coords[i])]
        else:
            current_part.append(list(line_coords[i]))
    
    if len(current_part) > 1:
        parts.append(current_part)
    
    return parts
```

Hmm, actually I realize there may be a subtlety. For the second part, should I use -180 or 180 as the antimeridian longitude? The GeoJSON spec uses [-180, 180] range, so -180 and 180 are the same meridian. I'll use 180 for consistency.

Actually, for GeoJSON that uses -180 to 180 range, when we're on the western side, -180 is more appropriate. Let me use -180 for the western part to maintain consistency.

But wait, both 180 and -180 represent the same line. Let me just use 180.0 consistently.

Actually, let me think again. The part west of the antimeridian has coordinates like -179.51, -179.0, -178.48. The antimeridian between these and the east side can be at either 180 or -180. Since the western points use negative longitudes, -180 fits better. Let me use -180 for the western part and 180 for the eastern part. Wait, but for the second part to start at -180: from -180 to -179.51, that's fine. But from 180 to -179.51... they'd be equivalent since 180 ≡ -180.

Actually, I'll just use 180.0 consistently. Let me look at what makes sense.

For the eastern part: coordinates are [178.94, 179.46, 179.97, 180.0] - all positive, makes sense.
For the western part: coordinates are [180.0, -179.51, -179.0, -178.48] - mixed signs... that's a bit odd.

Better to use -180 for the western part:
Eastern part: [178.94, 179.46, 179.97, 180.0]
Western part: [-180.0, -179.51, -179.0, -178.48]

Yes, this is cleaner. Let me code this up properly.

Let me write the full script.
1:04
Bash python3 -c "
python3 -c "
from pyproj import CRS
crs = CRS.from_epsg(3460)
print(crs)
print(crs.area_of_use)
"
EPSG:3460
- name: Fiji - onshore. Includes Viti Levu, Vanua Levu, Taveuni, the Yasawa Group, the Kadavu Group, the Lau Islands and Rotuma Islands.
- bounds: (176.81, -20.81, -178.15, -12.42)
2:40
Good - EPSG:3460 is Fiji 1986 / Fiji Map Grid, a metric grid covering all of Fiji including the areas that cross the antimeridian. Let me also check the bounds more closely - the area of use goes from 176.81 to -178.15 longitude, meaning it spans across the antimeridian region.

Now let me understand the problem. Some lines have coordinates that cross the antimeridian (the 180° meridian). In WGS84, when you have coordinates like:

[179.97..., -17.61...], [-179.51..., -17.59...]

These are actually close together in the Pacific (near Fiji), but because they're on opposite sides of the 180° line, a naive renderer would draw a line that goes all the way around the globe.

The fix is to "un-cross" the antimeridian by adding/subtracting 360° to make the coordinates continuous, then the geometry can be properly projected.

Let me think about the approach:

For features that cross the antimeridian (all those with `crosses_antimeridian_flag: true`), the coordinates go from positive longitudes (e.g., 178-179) to negative longitudes (e.g., -179 to -178). These are actually close in real space. The fix is to normalize the longitudes so they don't wrap around - e.g., convert -179 to +181 (by adding 360).

But we also need to be careful: some features have coordinates that are genuinely in the western part (like transect T011 which is at -176 to -177 longitude). Those don't cross the antimeridian.

Let me look at the specific crossings:

T001: [178.94, 179.46, 179.97, -179.51, -178.99, -178.48] - crosses antimeridian
T002: [179.07, 179.73, -179.62, -178.96] - crosses antimeridian
T003: [177.64, 178.46, 179.29, -179.89, -179.06] - crosses antimeridian
T004: [178.87, 179.54, -179.79, -179.12, -178.45, -177.78] - crosses antimeridian
T005: [177.60, 178.16, 178.72, 179.29, 179.85, -179.58, -179.02] - crosses antimeridian
T006: [178.62, 179.21, 179.79, -179.63, -179.04, -178.46, -177.88] - crosses antimeridian
T007: [177.92, 178.70, 179.47, -179.75, -178.97, -178.19] - crosses antimeridian
T008: [179.40, 179.67, 179.95, -179.78, -179.51, -179.23] - crosses antimeridian
T009: [178.07, 178.56, 179.05, 179.55, -179.96, -179.47] - crosses antimeridian
T010: [177.60, 179.13, -179.35, -177.82] - crosses antimeridian

For these, I need to shift the negative longitudes by +360 to make them continuous.

Wait, actually I need to think about this more carefully. The problem is that when rendering, a line from 179.97 to -179.51 will go the long way around the globe (east across the Atlantic) instead of the short way (just across the dateline).

The solution: convert to a coordinate system where the antimeridian is at 0 or at the edge. Or more simply, for coordinates that cross the antimeridian, add 360 to the negative longitude values so the sequence is continuous. Then when projecting to FMG, they'll be correctly placed.

After projecting, the coordinates will be in metres in the Fiji grid, so the antimeridian issue disappears.

Let me write a robust script:

1. For each feature, check if it crosses the antimeridian
2. If so, "unwrap" the longitudes by adding 360 to negative values (making them 180-360)
3. Then project to EPSG:3460
4. Calculate length in metres
5. Output as MultiLineString

But wait - I also need to handle the case where after unwrapping, we might need to split into multiple parts. Actually, let me think again...

For T001: [178.94, 179.46, 179.97, -179.51, -178.99, -178.48]
After unwrapping: [178.94, 179.46, 179.97, 180.49, 181.01, 181.52]

This is a continuous line that goes from 178.94 to 181.52 (i.e., crossing from 180 to 181). When projected to Fiji Map Grid, this should work fine as a single continuous line.

But what about T010: [177.60, 179.13, -179.35, -177.82]
After unwrapping: [177.60, 179.13, 180.65, 182.18]

That's also continuous.

What about T004: [178.87, 179.54, -179.79, -179.12, -178.45, -177.78]
After unwrapping: [178.87, 179.54, 180.21, 180.88, 181.55, 182.22]

Continuous too.

But wait, do all these actually need to be MultiLineString? Let me re-read the requirement.

"Every feature must come out as a MultiLineString, so do not leave any transects as plain LineString"

So every feature should be a MultiLineString. Even the ones that don't cross the antimeridian can be a MultiLineString with a single part.

But for the problematic ones (those crossing the antimeridian), they should be "multi-part geometries that faithfully represent the actual survey path."

Hmm, so actually the task asks me to identify which ones are problematic. The user says "some of the lines render as arcs spanning the entire globe instead of short local segments." Those are the ones crossing the antimeridian.

But actually, if I unwrap the longitudes and project, they should render correctly as single continuous lines. So why would they need to be multi-part?

Wait, I think the issue might be more subtle. Let me re-read: "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path."

Maybe the user wants me to split the line at the antimeridian, creating a multi-part geometry where each part is on one side of the dateline? That way each segment is rendered correctly.

Let me think about this differently. In the Fiji Map Grid (EPSG:3460), coordinates that are at longitudes 179°E and 179°W (which is 181°E in a continuous sense) are both within the grid. If I just unwrap and project, a single LineString from 179 to 181 (in continuous space) should project correctly to a continuous line in FMG.

Actually, I think the approach is simpler than I'm making it. Let me:

1. For features that cross the antimeridian, split the line at 180° longitude - this creates multiple line segments
2. Project each segment to FMG
3. Combine them into a MultiLineString
4. For features that don't cross, just project them and wrap in MultiLineString

Actually, splitting at 180° is complex. Let me think about a simpler approach.

The user says "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path." So for non-problematic ones, they can just be a single-part MultiLineString.

For the antimeridian-crossing ones, I need to split the line so each part doesn't cross the antimeridian. This would create multiple LineStrings that, when projected, correctly represent the survey path.

Let me think about how to do this:

For a line like T001: [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-178.99, -17.58], [-178.48, -17.58]

The line goes from 178.94 to 179.46 to 179.97 (eastward), then to -179.51 (which is 180.49 in continuous coordinates), then to -178.99 (181.01), then to -178.48 (181.52).

So the path is continuously eastward across the dateline. When I split at 180°, I'd get:
- Part 1: [178.94], [179.46], [179.97] and an interpolated point at (180, ~-17.60)
- Part 2: [180, ~-17.60], [-179.51], [-178.99], [-178.48]

Actually, to keep it simple, I could just:
1. For antimeridian-crossing features, unwrap the longitudes (add 360 to negative values)
2. Project to FMG
3. The result is a continuous line in FMG space
4. Output as MultiLineString with one part

But the user specifically says "the problematic ones must end up as multi-part geometries". So they want multiple parts.

Hmm, let me re-read: "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path"

Maybe they want me to keep the original coordinate system split, where each segment is in its proper hemisphere? Let me try a different interpretation.

Actually, I think the issue is about rendering in WGS84. A line that goes from 179.97 to -179.51 wraps around the globe. If you split it into two parts at the dateline, each part renders correctly:
- Part 1: [178.94, 179.46, 179.97] - east of 180
- Part 2: [-179.51, -178.99, -178.48] - west of 180 (but these are actually east of 180 in reality)

Wait, that doesn't quite work either. Let me think again...

Actually the real issue is that Fiji spans the antimeridian. Fiji's 180° meridian runs through the island group. So survey lines that cross the 180° meridian are actual real survey lines that cross from one side to the other.

For rendering in WGS84 (web maps), a line from 179.97 to -179.51 will be drawn across the entire globe instead of locally. The fix is to split the line at the antimeridian.

But the user wants the output in Fiji Map Grid (FMG), projected. In FMG, the antimeridian issue doesn't exist because it's a projected coordinate system. So the projection takes care of it.

Let me just do the simplest thing:
1. For each feature, split any segments that cross the dateline (where longitude sign changes and the longitude difference is > 180 in absolute terms... actually more simply, where the longitude goes from > 179 to < -179 or vice versa)
2. Actually, the simplest approach: detect antimeridian crossing, unwrap longitudes, project to FMG, then... hmm

Let me try a different approach. I'll use Shapely and GeoPandas to handle this properly.

Actually, the cleanest approach in Python:

1. Load the GeoJSON
2. For each feature:
   a. If it crosses the antimeridian (longitude changes sign abruptly):
      - Shift negative longitudes by +360 to make the line continuous
      - Project to FMG
      - The result should be a single continuous LineString in FMG
      - Convert to MultiLineString ([line])
   b. If it doesn't cross:
      - Just project to FMG
      - Convert to MultiLineString ([line])
3. Calculate length in metres
4. Write as GeoJSON

But wait, the user says "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path." This implies they should be split into multiple parts.

Let me re-interpret: Maybe the issue is about rendering in QGIS or a web map that doesn't handle the dateline well. By splitting the line at the antimeridian into multiple parts, each part can be rendered correctly even in WGS84 display. And then projecting to FMG keeps the geometry correct.

But actually, if we project to FMG, the projection handles the discontinuity naturally. A point at 179.97°E and a point at -179.51°W (which is 180.49°E) will project to adjacent locations in FMG.

Let me just go with a pragmatic approach:
1. For antimeridian-crossing features, add 360 to negative longitudes to make them continuous
2. Project the continuous line to FMG 
3. Output as MultiLineString with a single part

And for the non-crossing ones:
1. Project to FMG
2. Output as MultiLineString with a single part

Actually wait - the user says "the problematic ones must end up as multi-part geometries". This strongly suggests they want me to split the line. Let me split at the antimeridian.

For splitting at the antimeridian:
- I can iterate through coordinate pairs
- When I find a pair that jumps from positive to negative (or vice versa), I split there
- I need to interpolate a point at 180° longitude

Actually, I think the simplest robust approach is:

1. For each feature, check if it crosses the antimeridian
2. If it does:
   a. Unwrap longitudes (add 360 to negatives) to make continuous
   b. Split at 180° longitude boundary - creating segments that don't wrap
   c. For each segment, shift longitudes back to the -180 to 180 range if needed
   d. Actually this is getting complex

Let me think about this differently. In the FMG projection, there's no issue. So:
1. Unwrap negative longitudes by +360 for antimeridian-crossing features
2. Project to FMG
3. Keep as a single LineString wrapped in MultiLineString

OR

1. Split the line at the antimeridian (in WGS84 space)
2. Project each part to FMG
3. Combine into MultiLineString

I think option 1 is simpler and cleaner. Let me just go with that but make the non-problematic ones also MultiLineString.

Actually, you know what, let me just write the code and try it out. Let me start with the simpler unwrap-then-project approach, and see if it works well.

Let me write the script:

```python
import json
import geopandas as gpd
from shapely.geometry import LineString, MultiLineString, shape, mapping
from shapely.ops import transform
from pyproj import Transformer
import math

# Read the input
with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

# Transformer: WGS84 to Fiji Map Grid
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3460", always_xy=True)

def unwrap_longitudes(coords):
    """Make coordinates continuous across the antimeridian by adding 360 to negative longitudes where needed."""
    result = []
    prev_lon = coords[0][0]
    for lon, lat in coords:
        # If the longitude jumps from positive to negative (or vice versa) by > 180 degrees,
        # adjust by adding/subtracting 360
        while lon - prev_lon > 180:
            lon -= 360
        while lon - prev_lon < -180:
            lon += 360
        result.append((lon, lat))
        prev_lon = lon
    return result

features_out = []
for feat in data['features']:
    props = feat['properties']
    geom = shape(feat['geometry'])
    coords = list(geom.coords)
    
    # Check if it crosses the antimeridian
    crosses = props.get('crosses_antimeridian_flag', False)
    
    if crosses:
        # Unwrap longitudes
        unwrapped = unwrap_longitudes(coords)
        geom_cont = LineString(unwrapped)
    else:
        geom_cont = geom
    
    # Project to FMG
    geom_fmg = transform(transformer.transform, geom_cont)
    
    # Calculate length in metres
    length_m = geom_fmg.length
    
    # Convert to MultiLineString
    if geom_fmg.geom_type == 'LineString':
        geom_fmg = MultiLineString([geom_fmg])
    elif geom_fmg.geom_type == 'MultiLineString':
        pass  # already a MultiLineString
    
    feat_out = {
        "type": "Feature",
        "properties": {
            "transect_id": props['transect_id'],
            "vessel": props['vessel'],
            "survey_date": props['survey_date'],
            "length_m": round(length_m, 2)
        },
        "geometry": mapping(geom_fmg)
    }
    features_out.append(feat_out)

# Build output
output = {
    "type": "FeatureCollection",
    "name": "fiji_transects_fmg",
    "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3460" } },
    "features": features_out
}

with open('/work/fiji_transects_fmg.geojson', 'w') as f:
    json.dump(output, f, indent=2)

print("Done!")
```

Wait, but the user says "the problematic ones must end up as multi-part geometries that faithfully represent the actual survey path." This seems to say they must be truly multi-part, meaning the line is split into parts.

Let me reconsider. What if I split the line at the dateline?

For T001: coordinates go 178.94, 179.46, 179.97, -179.51, -178.99, -178.48

I split between 179.97 and -179.51. The line from 178.94 to 179.97 is one part. The line from -179.51 to -178.48 is another part. When projected to FMG, these two parts will be adjacent.

But actually, the user already knows these cross the antimeridian. In the FMG projection system, coordinates are projected correctly regardless of the antimeridian. So maybe the user wants the split to ensure that each part is properly projected?

Actually, I think the user's concern is about the rendering in WGS84 - some lines render as arcs spanning the globe. So in the original WGS84 data, they cross the antimeridian. When projected to FMG, this should be fine. But the user wants them as multi-part geometries - I think this means they want the antimeridian-crossing ones to be split into two or more LineStrings that each don't cross the antimeridian, then projected. This way, in FMG, you get distinct segments that faithfully represent the survey path.

Let me go with splitting at the antimeridian for those that cross it. Here's the refined approach:

For antimeridian-crossing features:
1. Iterate through coordinates
2. When a longitude jumps from > 0 to < 0 (or from < 0 to > 0), split there
3. Interpolate a point at the antimeridian (180°)
4. Project each part to FMG
5. Combine into MultiLineString

Actually, the simplest way: use shapely's `segmentize` or just the `normalize` approach. Let me think...

Another approach: Use `shapely.ops.split` to split at the dateline.

Let me just go with the approach where I split the line into segments at the dateline crossing points.

Actually, here's a simpler approach: 

For antimeridian-crossing features, I'll just split the LineString into parts at the dateline. The dateline in this region is 180° longitude. When the longitude goes from > 170 to < -170 (or similar large jump), we know it's crossing the dateline.

Let me code this up properly.

For splitting, I'll:
1. Go through coordinate pairs
2. If there's a crossing (longitude jumps from positive to negative or vice versa with a large gap), interpolate a point at 180° or -180°
3. Split the line into segments at these crossing points

Here's my refined script:

```python
import json
import geopandas as gpd
from shapely.geometry import LineString, MultiLineString, Point, mapping
from pyproj import Transformer
from copy import deepcopy

with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3460", always_xy=True)

def split_at_antimeridian(coords):
    """Split a list of coordinates at the antimeridian. Returns list of coordinate lists."""
    segments = []
    current_seg = [coords[0]]
    
    for i in range(1, len(coords)):
        lon1, lat1 = coords[i-1]
        lon2, lat2 = coords[i]
        
        # Check if this segment crosses the antimeridian
        # A crossing occurs when the absolute longitude difference is more than 180
        if abs(lon2 - lon1) > 180:
            # Interpolate point at longitude 180 (or -180)
            # The line from (lon1, lat1) to (lon2, lat2) crosses at lon=180 (or -180)
            # If lon1 < 0 and lon2 > 0, it crosses 180
            # We need to find the latitude at the crossing
            
            # Determine crossing longitude sign
            if lon1 < 0:
                # lon1 is negative (west), lon2 is positive (east) -> crosses 180
                # Actually, it crosses at 180 (which is -180 in the other direction)
                cross_lon = 180
            else:
                cross_lon = -180
                
            # Interpolate latitude at the crossing
            # Linear interpolation: lat = lat1 + (lat2-lat1) * (cross_lon - lon1) / (lon2 - lon1)
            # But we need to handle the wrap carefully
            # If lon1 < 0 and lon2 > 0:
            #   the longitude difference is (180 - lon1) + (lon2 - 180)... no wait
            # Let's use the actual longitude values
            
            # We need to adjust lon2 so we're measuring across the dateline
            # If lon1 < 0 and lon2 > 0, lon2 is less than 180, so the path goes:
            # from lon1 (e.g. -179) to -180 to 180 to lon2 (e.g. 179)
            # Actually no, that's wrong. -179 to 179 goes across 0, not across 180.
            
            # Let me reconsider. If lon1 = 179.97 and lon2 = -179.51,
            # the shortest path from 179.97 to -179.51 goes through 180.
            # The actual longitude difference is 179.97 - (-179.51) = 359.48
            # Going the other way: (180 - 179.97) + (180 - 179.51) = 0.03 + 0.49 = 0.52
            # So the shortest path crosses the antimeridian.
            
            # For interpolation along the shortest path crossing the dateline:
            # We can convert to continuous coordinates by adding 360 to negative values
            # Then interpolate at 180 (or equivalently at -180)
            
            if lon1 < 0:
                lon1_cont = lon1 + 360
            else:
                lon1_cont = lon1
            if lon2 < 0:
                lon2_cont = lon2 + 360
            else:
                lon2_cont = lon2
            
            # Now interpolate at 180
            if lon1_cont < lon2_cont:
                # Going eastward, crossing at 180
                frac = (180 - lon1_cont) / (lon2_cont - lon1_cont)
            else:
                # Going westward
                frac = (lon1_cont - 180) / (lon1_cont - lon2_cont)
            
            lat_cross = lat1 + (lat2 - lat1) * frac
            
            # Finish current segment at the crossing
            current_seg.append((cross_lon, lat_cross))
            segments.append(current_seg)
            
            # Start new segment from the crossing on the other side
            current_seg = [(cross_lon if cross_lon == -180 else -cross_lon, lat_cross)]
            # Actually, on the other side, the longitude at the crossing is
            # -180 if cross_lon was 180, or 180 if cross_lon was -180
            if cross_lon == 180:
                current_seg = [(-180, lat_cross), (lon2, lat2)]
            else:
                current_seg = [(180, lat_cross), (lon2, lat2)]
        else:
            current_seg.append((lon2, lat2))
    
    if current_seg:
        segments.append(current_seg)
    
    return segments
```

Hmm, this is getting complicated. Let me simplify.

Actually, let me think about what the user really wants. They say the problematic ones render as arcs spanning the globe. The fix is to make them multi-part. The simplest approach that faithfully represents the survey path:

For antimeridian-crossing lines, split each line at the antimeridian so each part is on one consistent side. Then project each part to FMG.

Let me write a cleaner version:

```python
def split_at_180(coords):
    """Given coordinates that cross the antimeridian, split into segments on each side."""
    # Convert to continuous longitudes by adding 360 to negative values
    cont_coords = [(lon + 360 if lon < 0 else lon, lat) for lon, lat in coords]
    
    # Now find where we cross 180 or 540 (which is 180+360=540)
    # Actually, since we added 360 to negatives, the continuous range is roughly [175, 185] for Fiji
    # Crossing points at 180 (if going from <180 to >180 or vice versa)
    
    segments = []
    current = [cont_coords[0]]
    
    for i in range(1, len(cont_coords)):
        lon1, lat1 = cont_coords[i-1]
        lon2, lat2 = cont_coords[i]
        
        # Check if we cross the 180 boundary in continuous space
        if (lon1 < 180 and lon2 > 180) or (lon1 > 180 and lon2 < 180):
            # Find interpolation point at 180
            frac = abs(180 - lon1) / abs(lon2 - lon1)
            lat_at_180 = lat1 + (lat2 - lat1) * frac
            
            # Finish current segment at 180
            current.append((180, lat_at_180))
            segments.append(current)
            
            # Start new segment at -180 (the other side)
            current = [(-180, lat_at_180), (lon2, lat2)]
        else:
            current.append((lon2, lat2))
    
    if current:
        segments.append(current)
    
    return segments
```

This should work better. Let me code the full solution now.

Actually wait, I also need to handle the coordinates that are < 0 but > -180 that DON'T cross the antimeridian (like T011 which is entirely in the western hemisphere at -176 to -177). Those shouldn't be unwrapped.

Let me look at which features cross the antimeridian:
- T001 through T010 have crosses_antimeridian_flag: true
- T011 through T030 have crosses_antimeridian_flag: false

So I can use the flag to decide.

For the features with the flag=true, the coordinates go from positive (e.g., 178) to negative (e.g., -179) which are actually just east and west of the dateline. Adding 360 to the negatives makes them continuous.

Let me write the script now.

Actually, wait - I need to consider the case where after splitting, some parts might be very short (just the interpolation point). Let me just make sure the logic handles this properly.

Let me write a clean script:

```python
import json
import geopandas as gpd
from shapely.geometry import LineString, MultiLineString, mapping
from pyproj import Transformer
import math

with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3460", always_xy=True)

def split_at_antimeridian(coords):
    """
    Split coordinates that cross the antimeridian (180° longitude) into segments.
    Each segment stays on one side of the dateline.
    Returns list of coordinate lists (each list is a segment).
    """
    # Convert to continuous longitudes: negative values become > 180
    cont = []
    for lon, lat in coords:
        if lon < 0:
            cont.append((lon + 360, lat))
        else:
            cont.append((lon, lat))
    
    segments = []
    current_seg = [coords[0]]  # start with original first coord
    
    for i in range(1, len(cont)):
        lon1, lat1 = cont[i-1]
        lon2, lat2 = cont[i]
        orig_lon2, orig_lat2 = coords[i]
        
        # Check if the segment crosses 180
        # In continuous space, crossing 180 means one value < 180 and the other > 180
        if (lon1 < 180 and lon2 > 180) or (lon1 > 180 and lon2 < 180):
            # Interpolate point at longitude 180
            if lon2 != lon1:
                frac = (180 - lon1) / (lon2 - lon1)
            else:
                frac = 0.5
            lat_at_180 = lat1 + (lat2 - lat1) * frac
            
            # The crossing point at 180° longitude
            # On the 'east' side (lon < 180), it's at +180
            # On the 'west' side (lon > 180), it's at -180
            crossing_east = (180, lat_at_180)   # for the eastern hemisphere part
            crossing_west = (-180, lat_at_180)  # for the western hemisphere part
            
            # Finish current segment at +180 (eastern side)
            current_seg.append(crossing_east)
            segments.append(current_seg)
            
            # Start new segment at -180 (western side)
            current_seg = [crossing_west, orig_lon2, orig_lat2]
        else:
            current_seg.append((orig_lon2, orig_lat2))
    
    if current_seg:
        segments.append(current_seg)
    
    return segments


features_out = []
for feat in data['features']:
    props = feat['properties']
    coords = feat['geometry']['coordinates']
    
    if props.get('crosses_antimeridian_flag', False):
        # Split at antimeridian into multiple segments
        segments = split_at_antimeridian(coords)
        
        # Project each segment to FMG
        projected_lines = []
        for seg_coords in segments:
            line = LineString(seg_coords)
            proj_line = transform(transformer.transform, line)
            projected_lines.append(proj_line)
        
        geom_fmg = MultiLineString(projected_lines)
    else:
        # Simple case: project directly
        line = LineString(coords)
        geom_fmg = transform(transformer.transform, line)
        if geom_fmg.geom_type == 'LineString':
            geom_fmg = MultiLineString([geom_fmg])
    
    # Calculate total length
    length_m = geom_fmg.length
    
    feat_out = {
        "type": "Feature",
        "properties": {
            "transect_id": props['transect_id'],
            "vessel": props['vessel'],
            "survey_date": props['survey_date'],
            "length_m": round(length_m, 2)
        },
        "geometry": mapping(geom_fmg)
    }
    features_out.append(feat_out)

output = {
    "type": "FeatureCollection",
    "name": "fiji_transects_fmg",
    "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::3460" } },
    "features": features_out
}

with open('/work/fiji_transects_fmg.geojson', 'w') as f:
    json.dump(output, f, indent=2)

print("Done!")
```

Wait, I need to import `transform` from shapely.ops. Let me also use `from shapely.ops import transform`. Let me also fix the coordinate handling for the new segment start.

Actually, let me clean up the split_at_antimeridian function. The logic for the new segment is a bit confused. Let me restructure:

```python
def split_at_antimeridian(coords):
    """
    Split coordinates that cross the antimeridian (180° longitude) into multiple segments.
    Each segment stays on one side of the dateline in WGS84.
    Returns list of coordinate lists.
    """
    # Convert to continuous longitudes: add 360 to negative values
    cont = [(lon + 360 if lon < 0 else lon, lat) for lon, lat in coords]
    
    segments = []
    current_seg = [coords[0]]
    
    for i in range(1, len(cont)):
        lon1, lat1 = cont[i-1]
        lon2, lat2 = cont[i]
        
        # Check if this segment crosses the 180° meridian in continuous space
        if (lon1 < 180 and lon2 > 180) or (lon1 > 180 and lon2 < 180):
            # Interpolate the crossing point at longitude 180
            if lon2 != lon1:
                frac = (180 - lon1) / (lon2 - lon1)
            else:
                frac = 0.5
            lat_at_180 = lat1 + (lat2 - lat1) * frac
            
            # Finish the current segment on the eastern side (lon < 180)
            current_seg.append((180, lat_at_180))
            segments.append(current_seg)
            
            # Start new segment on the western side (lon > 180 / negative)
            # The next original coord is at orig_lon2, but we need to decide which side it's on
            # If lon2 > 180, then orig_lon2 < 0 (western side)
            current_seg = [(-180, lat_at_180), coords[i]]
        else:
            current_seg.append(coords[i])
    
    if current_seg:
        segments.append(current_seg)
    
    return segments
```

Wait, `coords[i]` for the new segment start... Let me track this more carefully.

For T001: coords = [(178.94, -17.63), (179.46, -17.62), (179.97, -17.61), (-179.51, -17.60), (-178.99, -17.58), (-178.48, -17.58)]

cont = [(178.94, -17.63), (179.46, -17.62), (179.97, -17.61), (180.49, -17.60), (181.01, -17.58), (181.52, -17.58)]

Going through pairs:
1. (178.94, -17.63) -> (179.46, -17.62): both < 180, no crossing. current_seg = [(178.94, -17.63), (179.46, -17.62)]
2. (179.46, -17.62) -> (179.97, -17.61): both < 180, no crossing. current_seg = [(178.94, -17.63), (179.46, -17.62), (179.97, -17.61)]
3. (179.97, -17.61) -> (180.49, -17.60): lon1=179.97<180, lon2=180.49>180 -> CROSSING
   frac = (180 - 179.97) / (180.49 - 179.97) = 0.03 / 0.52 = 0.0577
   lat_at_180 = -17.61 + (-17.60 - (-17.61)) * 0.0577 = -17.61 + 0.01 * 0.0577 = -17.6094
   current_seg ends with (180, -17.6094)
   segments = [[(178.94, -17.63), (179.46, -17.62), (179.97, -17.61), (180, -17.6094)]]
   New current_seg = [(-180, -17.6094), (-179.51, -17.60)]

4. (-179.51, -17.60) -> (-178.99, -17.58): In cont: (180.49, -17.60) -> (181.01, -17.58)
   Both > 180, no crossing.
   current_seg = [(-180, -17.6094), (-179.51, -17.60), (-178.99, -17.58)]
   
5. (-178.99, -17.58) -> (-178.48, -17.58): In cont: (181.01, -17.58) -> (181.52, -17.58)
   Both > 180, no crossing.
   current_seg = [(-180, -17.6094), (-179.51, -17.60), (-178.99, -17.58), (-178.48, -17.58)]

segments = [[(178.94, -17.63), (179.46, -17.62), (179.97, -17.61), (180, -17.6094)], 
            [(-180, -17.6094), (-179.51, -17.60), (-178.99, -17.58), (-178.48, -17.58)]]

These are two LineStrings that meet at the antimeridian. When projected to FMG, they should form a continuous path.

OK, this looks good. Let me write the final script.
2:40
Bash python3 << 'PYEOF'
python3 << 'PYEOF'
import json
from shapely.geometry import LineString, MultiLineString, mapping
from shapely.ops import transform
from pyproj import Transformer

with open('/work/fiji_transects_wgs84.geojson', 'r') as f:
    data = json.load(f)

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3460", always_xy=True)

def split_at_antimeridian(coords):
    """
    Split coordinates that cross the 180° meridian into multiple segments,
    each staying on one side of the dateline in WGS84.
    """
    # Convert to continuous longitudes: add 360 to negative values
    cont = [(lon + 360 if lon < 0 else lon, lat) for lon, lat in coords]

    segments = []
    current_seg = [coords[0]]

    for i in range(1, len(cont)):
        lon1, lat1 = cont[i-1]
        lon2, lat2 = cont[i]

        # Check if this segment crosses the 180° meridian in continuous space
        if (lon1 < 180 and lon2 > 180) or (lon1 > 180 and lon2 < 180):
            # Interpolate the crossing point at longitude 180
            if lon2 != lon1:
                frac = (180 - lon1) / (lon2 - lon1)
            else:
                frac = 0.5
            lat_at_180 = lat1 + (lat2 - lat1) * frac

            # Finish the current segment on the eastern side
            current_seg.append((180, lat_at_180))
            segments.append(current_seg)

            # Start new segment on the western side
            current_seg = [(-180, lat_at_180), coords[i]]
        else:
            current_seg.append(coords[i])

    if current_seg:
        segments.append(current_seg)

    return segments


features_out = []
for feat in data['features']:
    props = feat['properties']
    coords = feat['geometry']['coordinates']

    if props.get('crosses_antimeridian_flag', False):
        # Split at antimeridian into multiple parts
        seg_coords_list = split_at_antimeridian(coords)
        projected = []
        for seg in seg_coords_list:
            line = LineString(seg)
            proj = transform(transformer.transform, line)
            projected.append(proj)
        geom_fmg = MultiLineString(projected)
    else:
        # Simple: project directly, wrap in MultiLineString
        line = LineString(coords)
        proj = transform(transformer.transform, line)
        geom_fmg = MultiLineString([proj])

    length_m = round(geom_fmg.length, 2)

    features_out.append({
        "type": "Feature",
        "properties": {
            "transect_id": props['transect_id'],
            "vessel": props['vessel'],
            "survey_date": props['survey_date'],
            "length_m": length_m
        },
        "geometry": mapping(geom_fmg)
    })

output = {
    "type": "FeatureCollection",
    "name": "fiji_transects_fmg",
    "crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::3460"}},
    "features": features_out
}

with open('/work/fiji_transects_fmg.geojson', 'w') as f:
    json.dump(output, f, indent=2)

# Quick validation
print(f"Total features: {len(features_out)}")
for ft in features_out:
    gtype = ft['geometry']['type']
    nparts = len(ft['geometry']['coordinates'])
    tid = ft['properties']['transect_id']
    print(f"  {tid}: {gtype} ({nparts} part{'s' if nparts > 1 else ''}), length={ft['properties']['length_m']} m")
PYEOF
Total features: 30
  T001: MultiLineString (2 parts), length=273335.11 m
  T002: MultiLineString (2 parts), length=216575.29 m
  T003: MultiLineString (2 parts), length=351823.46 m
  T004: MultiLineString (2 parts), length=372666.09 m
  T005: MultiLineString (2 parts), length=362394.57 m
  T006: MultiLineString (2 parts), length=393605.56 m
  T007: MultiLineString (2 parts), length=414594.38 m
  T008: MultiLineString (2 parts), length=146792.55 m
  T009: MultiLineString (2 parts), length=263388.29 m
  T010: MultiLineString (2 parts), length=489842.48 m
  T011: MultiLineString (1 part), length=78492.17 m
  T012: MultiLineString (1 part), length=84537.56 m
  T013: MultiLineString (1 part), length=179800.48 m
  T014: MultiLineString (1 part), length=148392.88 m
  T015: MultiLineString (1 part), length=124738.16 m
  T016: MultiLineString (1 part), length=53310.24 m
  T017: MultiLineString (1 part), length=152432.38 m
  T018: MultiLineString (1 part), length=112421.62 m
  T019: MultiLineString (1 part), length=179435.73 m
  T020: MultiLineString (1 part), length=215352.23 m
  T021: MultiLineString (1 part), length=230219.24 m
  T022: MultiLineString (1 part), length=50981.78 m
  T023: MultiLineString (1 part), length=81980.09 m
  T024: MultiLineString (1 part), length=251074.91 m
  T025: MultiLineString (1 part), length=108459.05 m
  T026: MultiLineString (1 part), length=100661.48 m
  T027: MultiLineString (1 part), length=130089.67 m
  T028: MultiLineString (1 part), length=173047.04 m
  T029: MultiLineString (1 part), length=205262.64 m
  T030: MultiLineString (1 part), length=163829.39 m
2:58
Let me verify the output file looks correct, especially for the antimeridian-crossing features. Let me also do a sanity check on the geometry - the issue was that lines crossing the antimeridian render as arcs spanning the globe. The approach I used splits at the 180° meridian, creating two segments: one on the east side (ending at 180°), and one on the west side (starting at -180°). But let me double-check the logic more carefully.

Looking at the coordinates for T001:
- [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [-179.51, -17.60], [-179.00, -17.58], [-178.48, -17.58]

Converting to continuous (add 360 to negatives):
- [178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [180.49, -17.60], [181.00, -17.58], [181.52, -17.58]

So the transition from 179.97 to 180.49 crosses 180. Let me check the split logic:

For i=1: lon_prev=178.94, lon_curr=179.46 - no crossing
For i=2: lon_prev=179.46, lon_curr=179.97 - no crossing
For i=3: lon_prev=179.97 (<180), lon_curr=180.49 (>180) - crossing!

frac = (180-179.97)/(180.49-179.97) = 0.03/0.52 ≈ 0.0577
lat at 180 = -17.61 + (-17.60 - -17.61)*0.0577 = -17.61 + 0.01*0.0577 ≈ -17.6094

So current_seg has: [179.97, -17.61], [180, -17.6094]
segments has: [[178.94, -17.63], [179.46, -17.62], [179.97, -17.61], [180, -17.6094]]

New seg starts: [-180, -17.6094], [-179.51, -17.60]
Then continues: [-179.00, -17.58], [-178.48, -17.58]

That looks correct! Each segment is on one side of the meridian.

But wait - the interpolated point at 180°/-180° should be the same point. The issue is that 180° longitude in EPSG:4326 is the same as -180° longitude, but when we project to EPSG:3460, these might not map to the same location. Let me check...

Actually, there could be a subtle issue. The Fiji Map Grid (EPSG:3460) has bounds (176.81, -20.81, -178.15, -12.42). So it goes from east of 180° (176.81) to west of 180° (-178.15). The question is whether 180° and -180° project to the same point or not.

Actually, the interpolation at 180°/-180° should work fine because both (180, lat) and (-180, lat) represent the same meridian. But in the transformer, they might be treated differently depending on how the transform handles longitudes near the antimeridian.

Let me think about this more carefully. The Fiji Map Grid is probably a Transverse Mercator projection centered on Fiji. The antimeridian (180°) is actually not that far from Fiji. Let me check if 180° and -180° map to the same coordinates in EPSG:3460.

Actually, I should test this to make sure there's no discontinuity.

But looking at the output lengths, they seem reasonable for survey transects. Let me verify the output geometry for a couple of antimeridian-crossing features.
2:58
2:58
Bash python3 << 'PYEOF'
python3 << 'PYEOF'
import json
from shapely.geometry import Point, LineString, MultiLineString
from shapely.ops import transform
from pyproj import Transformer

transformer = Transformer.from_crs("EPSG:4326", "EPSG:3460", always_xy=True)

# Test: do 180 and -180 map to same point?
p180 = Point(180, -17.6)
pn180 = Point(-180, -17.6)
p180_proj = transform(transformer.transform, p180)
pn180_proj = transform(transformer.transform, pn180)
print(f"Point(180, -17.6) projected: {p180_proj}")
print(f"Point(-180, -17.6) projected: {pn180_proj}")
print(f"Distance: {p180_proj.distance(pn180_proj):.2f} m")

# Also check the existing feature T001
with open('/work/fiji_transects_fmg.geojson', 'r') as f:
    data = json.load(f)

t001 = data['features'][0]
print(f"\nT001 geometry type: {t001['geometry']['type']}")
coords = t001['geometry']['coordinates']
for i, part in enumerate(coords):
    print(f"  Part {i}: {part[0]} ... {part[-1]}")
print(f"  Length: {t001['properties']['length_m']} m")
PYEOF
Point(180, -17.6) projected: POINT (2132648.8359037815 3933164.758703781)
Point(-180, -17.6) projected: POINT (2132648.835903784 3933164.758703781)
Distance: 0.00 m

T001 geometry type: MultiLineString
  Part 0: [2020343.340583844, 3930017.4674364463] ... [2132641.3519591475, 3932030.6236885176]
  Part 1: [2132641.3519591503, 3932030.6236885176] ... [2293630.6493575512, 3933970.5213900264]
  Length: 273335.11 m
3:10
The approach seems correct - points at 180° and -180° project to the same location (or within ~0.001m), so the split parts will connect properly at the antimeridian. The geometry looks good.

Let me also verify a few more things:
1. All features are MultiLineString ✓
2. All features have length_m ✓
3. All original attributes carried through ✓
4. Non-antimeridian features are also MultiLineString (with 1 part) ✓

Let me do a final validation to make sure the output GeoJSON is valid.
3:10
Bash python3 << 'PYEOF'
python3 << 'PYEOF'
import json

with open('/work/fiji_transects_fmg.geojson', 'r') as f:
    data = json.load(f)

# Validate all features
issues = []
for feat in data['features']:
    props = feat['properties']
    geom = feat['geometry']
    
    # Check geometry type
    if geom['type'] != 'MultiLineString':
        issues.append(f"{props['transect_id']}: geometry is {geom['type']}, not MultiLineString")
    
    # Check required properties
    for key in ['transect_id', 'vessel', 'survey_date', 'length_m']:
        if key not in props:
            issues.append(f"{props['transect_id']}: missing {key}")
    
    # Check transect_id preserved
    orig = {'T001','T002','T003','T004','T005','T006','T007','T008','T009','T010','T011','T012','T013','T014','T015','T016','T017','T018','T019','T020','T021','T022','T023','T024','T025','T026','T027','T028','T029','T030'}
    if props['transect_id'] not in orig:
        issues.append(f"Unknown transect_id: {props['transect_id']}")

    # Check length is positive
    if props['length_m'] <= 0:
        issues.append(f"{props['transect_id']}: length_m is {props['length_m']}")

    # Check MultiLineString has at least one part
    if not geom['coordinates']:
        issues.append(f"{props['transect_id']}: empty MultiLineString")

    for part in geom['coordinates']:
        if len(part) < 2:
            issues.append(f"{props['transect_id']}: part with <2 points")

if issues:
    print("ISSUES FOUND:")
    for i in issues:
        print(f"  - {i}")
else:
    print("ALL VALIDATIONS PASSED")
    print(f"Total features: {len(data['features'])}")
    print(f"All are MultiLineString: {all(f['geometry']['type']=='MultiLineString' for f in data['features'])}")
    print(f"All have length_m > 0: {all(f['properties']['length_m']>0 for f in data['features'])}")
    
    # Count multi-part vs single-part
    n_multi = sum(1 for f in data['features'] if len(f['geometry']['coordinates'])>1)
    n_single = sum(1 for f in data['features'] if len(f['geometry']['coordinates'])==1)
    print(f"Multi-part: {n_multi}, Single-part: {n_single}")
PYEOF
ALL VALIDATIONS PASSED
Total features: 30
All are MultiLineString: True
All have length_m > 0: True
Multi-part: 10, Single-part: 20
3:28
The results look correct. Let me also quickly verify that the crossing points are handled properly - when we split at the antimeridian, we should insert a point at lon=180 (east side) and lon=-180 (west side), and these should project to the exact same point in FMG. The test above confirmed that.

Let me also check the original data's geometry to make sure the split is correct. For example, T001 has coordinates:
[ 178.941..., -17.632... ], 
[ 179.456..., -17.623... ], 
[ 179.971..., -17.611... ], 
[ -179.513..., -17.596... ], 
[ -178.999..., -17.579... ], 
[ -178.484..., -17.577... ]

So the split point should be between [179.971, -17.611] and [-179.513, -17.596]. In continuous coordinates:
- 179.971 and (-179.513 + 360) = 180.487
So it crosses 180. The fractional distance is (180 - 179.971) / (180.487 - 179.971) = 0.029 / 0.516 ≈ 0.0562

The lat at crossing: -17.611 + (-17.596 - (-17.611)) * 0.0562 = -17.611 + 0.015 * 0.0562 ≈ -17.6102

So we'd get two segments:
1: [178.941, -17.632], [179.456, -17.623], [179.971, -17.611], [180, -17.610]
2: [-180, -17.610], [-179.513, -17.596], [-178.999, -17.579], [-178.484, -17.577]

Both 180 and -180 project to the same FMG point, so the MultiLineString is continuous. Good.

Wait, but I should double check my split_at_antimeridian function. The issue is that the original data has coordinates in the range [-180, 180]. When we split, we need to check consecutive points where one is on one side of the dateline and the other is on the other side. 

Let me trace through T001:
- cont[0] = (178.941, -17.632) - east of 180
- cont[1] = (179.456, -17.623) - east
- cont[2] = (179.971, -17.611) - east
- cont[3] = (-179.513+360=180.487, -17.596) - west of 180 in continuous space

So at i=3: lon1=179.971 (east), lon2=180.487 (west in continuous). Since lon1 < 180 and lon2 > 180, we detect a crossing.

frac = (180 - 179.971) / (180.487 - 179.971) = 0.029 / 0.516 ≈ 0.0562
lat_at_180 = -17.611 + (-17.596 + 17.611) * 0.0562 = -17.611 + 0.015 * 0.0562 ≈ -17.6102

Then:
current_seg = [coords[0], coords[1], coords[2], (180, -17.6102)]  -- actually these are original coords, not cont
Wait, current_seg starts with coords[0] = (178.941, -17.632) and then appends coords[1], coords[2] normally. Then at i=3 we detect crossing, append (180, lat_at_180).

segments[-1] = [(178.941, -17.632), (179.456, -17.623), (179.971, -17.611), (180, -17.6102)]

Start new segment: current_seg = [(-180, -17.6102), coords[3]]
coords[3] = (-179.513, -17.596)

Continue... i=4: cont[3]=180.487, cont[4]=(-178.999+360=181.001). Both > 180, no crossing. Append coords[4]=(-178.999, -17.579)
i=5: cont[4]=181.001, cont[5]=(-178.484+360=181.516). Both > 180, no crossing. Append coords[5]=(-178.484, -17.577)

End: segments[-1] = [(-180, -17.6102), (-179.513, -17.596), (-178.999, -17.579), (-178.484, -17.577)]

This looks correct! The two segments connect at the exact same geographic point (180/-180, lat) which projects to the same FMG coordinate.

The output looks good. Let me just read the final file to confirm it's nicely formatted.
3:28
3:28
Read /work/fiji_transects_fmg.geojson
     1	{
     2	  "type": "FeatureCollection",
     3	  "name": "fiji_transects_fmg",
     4	  "crs": {
     5	    "type": "name",
     6	    "properties": {
     7	      "name": "urn:ogc:def:crs:EPSG::3460"
     8	    }
     9	  },
    10	  "features": [
    11	    {
    12	      "type": "Feature",
    13	      "properties": {
    14	        "transect_id": "T001",
    15	        "vessel": "Taveuni II",
    16	        "survey_date": "2025-08-15",
    17	        "length_m": 273335.11
    18	      },
    19	      "geometry": {
    20	        "type": "MultiLineString",
    21	        "coordinates": [
    22	          [
    23	            [
    24	              2020343.340583844,
    25	              3930017.4674364463
    26	            ],
    27	            [
    28	              2074969.3287187445,
    29	              3930832.111936373
    30	            ],
    31	            [
    32	              2129608.4030453707,
    33	              3931959.043532026
    34	            ],
    35	            [
    36	              2132641.3519591475,
    37	              3932030.6236885176
    38	            ]
    39	          ],
    40	          [
    41	            [
    42	              2132641.3519591503,
    43	              3932030.6236885176
    44	            ],
    45	            [
    46	              2184265.933617849,
    47	              3933178.8742693216
    48	            ],
    49	            [
    50	              2238946.8792888806,
    51	              3934438.0861816974
    52	            ],
    53	            [
    54	              2293630.6493575512,
    55	              3933970.5213900264
    56	            ]
    57	          ]
    58	        ]
    59	      }
    60	    },
<system-reminder>Showed lines 1-60 of 1256. Re-call Read with offset=61 to continue, or raise limit.</system-reminder>
3:38
The output looks good! Let me verify a few more things to be thorough. The key requirements were:

1. ✅ Fix geometries so they render correctly (split at antimeridian so arcs don't span the globe)
2. ✅ Convert to Fiji's national metric grid (EPSG:3460 - Fiji 1986 / Fiji Map Grid)
3. ✅ Each transect as one MultiLineString feature
4. ✅ With a length_m attribute in metres
5. ✅ All features are MultiLineString (no plain LineString)
6. ✅ Problematic ones (crossing antimeridian) are multi-part geometries
7. ✅ Survey attributes (vessel and survey_date) carried through unchanged
8. ✅ transect_id as key field
9. ✅ Written as GeoJSON named fiji_transects_fmg.geojson

The split at antimeridian is done correctly:
- The continuous coordinate space splits at 180° longitude
- Points at 180° and -180° project to the same point in EPSG:3460 (verified)
- Each part stays on one side of the meridian
- The crossing point is interpolated properly

Everything looks correct!
3:38