Skip to content

Commit 9ad9824

Browse files
committed
msProjectShapeLine(): take into account antimeridian when reprojecting lines from Polar Stereographic to geographic / WebMercator
1 parent 44dde80 commit 9ad9824

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

mapproject.c

+140
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ struct reprojectionObj
6666
projectionObj* in;
6767
projectionObj* out;
6868
PJ* pj;
69+
int should_do_line_cutting;
70+
shapeObj splitShape;
6971
int bFreePJ;
7072
};
7173

@@ -366,6 +368,7 @@ reprojectionObj* msProjectCreateReprojector(projectionObj* in, projectionObj* ou
366368
reprojectionObj* obj = (reprojectionObj*)msSmallCalloc(1, sizeof(reprojectionObj));
367369
obj->in = in;
368370
obj->out = out;
371+
obj->should_do_line_cutting = -1;
369372

370373
/* -------------------------------------------------------------------- */
371374
/* If the source and destination are simple and equal, then do */
@@ -448,6 +451,7 @@ void msProjectDestroyReprojector(reprojectionObj* reprojector)
448451
return;
449452
if( reprojector->bFreePJ )
450453
proj_destroy(reprojector->pj);
454+
msFreeShape(&(reprojector->splitShape));
451455
msFree(reprojector);
452456
}
453457

@@ -490,6 +494,8 @@ struct reprojectionObj
490494
{
491495
projectionObj* in;
492496
projectionObj* out;
497+
int should_do_line_cutting;
498+
shapeObj splitShape;
493499
int no_op;
494500
};
495501

@@ -503,6 +509,7 @@ reprojectionObj* msProjectCreateReprojector(projectionObj* in, projectionObj* ou
503509
obj = (reprojectionObj*)msSmallCalloc(1, sizeof(reprojectionObj));
504510
obj->in = in;
505511
obj->out = out;
512+
obj->should_do_line_cutting = -1;
506513

507514
/* -------------------------------------------------------------------- */
508515
/* If the source and destination are equal, then do nothing. */
@@ -548,6 +555,7 @@ void msProjectDestroyReprojector(reprojectionObj* reprojector)
548555
{
549556
if( !reprojector )
550557
return;
558+
msFreeShape(&(reprojector->splitShape));
551559
msFree(reprojector);
552560
}
553561

@@ -1134,6 +1142,96 @@ static int msProjectSegment( reprojectionObj* reprojector,
11341142
return MS_SUCCESS;
11351143
}
11361144

1145+
/************************************************************************/
1146+
/* msProjectShapeShouldDoLineCutting() */
1147+
/************************************************************************/
1148+
1149+
/* Detect projecting from north polar stereographic to longlat or EPSG:3857 */
1150+
#ifdef USE_GEOS
1151+
static int msProjectShapeShouldDoLineCutting(reprojectionObj* reprojector)
1152+
{
1153+
if( reprojector->should_do_line_cutting >= 0)
1154+
return reprojector->should_do_line_cutting;
1155+
1156+
projectionObj *in = reprojector->in;
1157+
projectionObj *out = reprojector->out;
1158+
if( !(!in->gt.need_geotransform && !msProjIsGeographicCRS(in) &&
1159+
(msProjIsGeographicCRS(out) ||
1160+
(out->numargs == 1 && strcmp(out->args[0], "init=epsg:3857") == 0))) )
1161+
{
1162+
reprojector->should_do_line_cutting = MS_FALSE;
1163+
return MS_FALSE;
1164+
}
1165+
1166+
int srcIsPolar;
1167+
double extremeLongEasting;
1168+
if( msProjIsGeographicCRS(out) )
1169+
{
1170+
pointObj p;
1171+
double gt3 = out->gt.need_geotransform ? out->gt.geotransform[3] : 0.0;
1172+
double gt4 = out->gt.need_geotransform ? out->gt.geotransform[4] : 0.0;
1173+
double gt5 = out->gt.need_geotransform ? out->gt.geotransform[5] : 1.0;
1174+
p.x = 0.0;
1175+
p.y = 0.0;
1176+
srcIsPolar = msProjectPointEx(reprojector, &p) == MS_SUCCESS &&
1177+
fabs(gt3 + p.x * gt4 + p.y * gt5 - 90) < 1e-8;
1178+
extremeLongEasting = 180;
1179+
}
1180+
else
1181+
{
1182+
pointObj p1;
1183+
pointObj p2;
1184+
double gt1 = out->gt.need_geotransform ? out->gt.geotransform[1] : 1.0;
1185+
p1.x = 0.0;
1186+
p1.y = -0.1;
1187+
p2.x = 0.0;
1188+
p2.y = 0.1;
1189+
srcIsPolar = msProjectPointEx(reprojector, &p1) == MS_SUCCESS &&
1190+
msProjectPointEx(reprojector, &p2) == MS_SUCCESS &&
1191+
fabs((p1.x - p2.x) * gt1) > 20e6;
1192+
extremeLongEasting = 20037508.3427892;
1193+
}
1194+
if( !srcIsPolar )
1195+
{
1196+
reprojector->should_do_line_cutting = MS_FALSE;
1197+
return MS_FALSE;
1198+
}
1199+
1200+
pointObj p;
1201+
double invgt0 = out->gt.need_geotransform ? out->gt.invgeotransform[0] : 0.0;
1202+
double invgt1 = out->gt.need_geotransform ? out->gt.invgeotransform[1] : 1.0;
1203+
double invgt3 = out->gt.need_geotransform ? out->gt.invgeotransform[3] : 0.0;
1204+
double invgt4 = out->gt.need_geotransform ? out->gt.invgeotransform[4] : 0.0;
1205+
1206+
lineObj newLine = {0,NULL};
1207+
const double EPS = 1e-10;
1208+
1209+
p.x = invgt0 + -extremeLongEasting * (1 - EPS) * invgt1;
1210+
p.y = invgt3 + -extremeLongEasting * (1 - EPS) * invgt4;
1211+
msProjectPoint(out, in, &p);
1212+
pointObj first = p;
1213+
msAddPointToLine(&newLine, &p );
1214+
1215+
p.x = invgt0 + extremeLongEasting * (1 - EPS) * invgt1;
1216+
p.y = invgt3 + extremeLongEasting * (1 - EPS) * invgt4;
1217+
msProjectPoint(out, in, &p);
1218+
msAddPointToLine(&newLine, &p );
1219+
1220+
p.x = 0;
1221+
p.y = 0;
1222+
msAddPointToLine(&newLine, &p );
1223+
1224+
msAddPointToLine(&newLine, &first );
1225+
1226+
msInitShape(&(reprojector->splitShape));
1227+
reprojector->splitShape.type = MS_SHAPE_POLYGON;
1228+
msAddLineDirectly(&(reprojector->splitShape), &newLine);
1229+
1230+
reprojector->should_do_line_cutting = MS_TRUE;
1231+
return MS_TRUE;
1232+
}
1233+
#endif
1234+
11371235
/************************************************************************/
11381236
/* msProjectShapeLine() */
11391237
/* */
@@ -1197,7 +1295,49 @@ msProjectShapeLine(reprojectionObj* reprojector,
11971295
#undef p_y
11981296
#endif
11991297

1298+
#ifdef USE_GEOS
1299+
if( shape->type == MS_SHAPE_LINE &&
1300+
msProjectShapeShouldDoLineCutting(reprojector) )
1301+
{
1302+
shapeObj tmpShapeInputLine;
1303+
msInitShape(&tmpShapeInputLine);
1304+
tmpShapeInputLine.type = MS_SHAPE_LINE;
1305+
tmpShapeInputLine.numlines = 1;
1306+
tmpShapeInputLine.line = line;
1307+
1308+
shapeObj* diff = NULL;
1309+
if( msGEOSIntersects(&tmpShapeInputLine, &(reprojector->splitShape)) )
1310+
{
1311+
diff = msGEOSDifference(&tmpShapeInputLine, &(reprojector->splitShape));
1312+
}
12001313

1314+
tmpShapeInputLine.numlines = 0;
1315+
tmpShapeInputLine.line = NULL;
1316+
msFreeShape(&tmpShapeInputLine);
1317+
1318+
if( diff )
1319+
{
1320+
for(int j = 0; j < diff->numlines; j++ )
1321+
{
1322+
for( i=0; i < diff->line[j].numpoints; i++ ) {
1323+
msProjectPointEx(reprojector, &(diff->line[j].point[i]));
1324+
}
1325+
if( j == 0 )
1326+
{
1327+
line_out->numpoints = diff->line[j].numpoints;
1328+
memcpy(line_out->point, diff->line[0].point, sizeof(pointObj) * line_out->numpoints);
1329+
}
1330+
else
1331+
{
1332+
msAddLineDirectly(shape, &(diff->line[j]));
1333+
}
1334+
}
1335+
msFreeShape(diff);
1336+
msFree(diff);
1337+
return MS_SUCCESS;
1338+
}
1339+
}
1340+
#endif
12011341

12021342
wrap_test = out != NULL && out->proj != NULL && msProjIsGeographicCRS(out)
12031343
&& !msProjIsGeographicCRS(in);

msautotest/mspython/bug_check.py

+94
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,103 @@ def test_pattern():
123123

124124
return 'success'
125125

126+
###############################################################################
127+
# Test reprojection of lines from Polar Stereographic and crossing the antimerdian
128+
129+
def test_reprojection_lines_from_polar_stereographic_to_geographic():
130+
131+
shape = mapscript.shapeObj( mapscript.MS_SHAPE_LINE )
132+
line = mapscript.lineObj()
133+
line.add( mapscript.pointObj( -5554682.77568025, -96957.3485051449 ) ) # -179 40
134+
line.add( mapscript.pointObj( -5554682.77568025, 96957.3485051449 ) ) # 179 40
135+
shape.add( line )
136+
137+
polar_proj = mapscript.projectionObj("+proj=stere +lat_0=90 +lat_ts=60 +lon_0=270 +datum=WGS84")
138+
longlat_proj = mapscript.projectionObj("+proj=longlat +datum=WGS84")
139+
140+
if shape.project(polar_proj, longlat_proj) != 0:
141+
pmstestlib.post_reason('shape.project() failed')
142+
return 'fail'
143+
144+
part1 = shape.get(0)
145+
part2 = shape.get(1)
146+
if not part1 or not part2:
147+
pmstestlib.post_reason('should get two parts')
148+
return 'fail'
149+
150+
point11 = part1.get(0)
151+
point12 = part1.get(1)
152+
point21 = part2.get(0)
153+
point22 = part2.get(1)
154+
if abs(point11.x - -179.0) > 1e-7:
155+
print(point11.x, point12.x, point21.x, point22.x)
156+
pmstestlib.post_reason('did not get expected coordinates')
157+
return 'fail'
158+
if abs(point12.x - -180.0) > 1e-7:
159+
print(point11.x, point12.x, point21.x, point22.x)
160+
pmstestlib.post_reason('did not get expected coordinates')
161+
return 'fail'
162+
if abs(point21.x - 180.0) > 1e-7:
163+
print(point11.x, point12.x, point21.x, point22.x)
164+
pmstestlib.post_reason('did not get expected coordinates')
165+
return 'fail'
166+
if abs(point22.x - 179.0) > 1e-7:
167+
print(point11.x, point12.x, point21.x, point22.x)
168+
pmstestlib.post_reason('did not get expected coordinates')
169+
return 'fail'
170+
return 'success'
171+
172+
###############################################################################
173+
# Test reprojection of lines from Polar Stereographic and crossing the antimerdian
174+
175+
def test_reprojection_lines_from_polar_stereographic_to_webmercator():
176+
177+
shape = mapscript.shapeObj( mapscript.MS_SHAPE_LINE )
178+
line = mapscript.lineObj()
179+
line.add( mapscript.pointObj( -5554682.77568025, -96957.3485051449 ) ) # -179 40
180+
line.add( mapscript.pointObj( -5554682.77568025, 96957.3485051449 ) ) # 179 40
181+
shape.add( line )
182+
183+
polar_proj = mapscript.projectionObj("+proj=stere +lat_0=90 +lat_ts=60 +lon_0=270 +datum=WGS84")
184+
longlat_proj = mapscript.projectionObj("init=epsg:3857")
185+
186+
if shape.project(polar_proj, longlat_proj) != 0:
187+
pmstestlib.post_reason('shape.project() failed')
188+
return 'fail'
189+
190+
part1 = shape.get(0)
191+
part2 = shape.get(1)
192+
if not part1 or not part2:
193+
pmstestlib.post_reason('should get two parts')
194+
return 'fail'
195+
196+
point11 = part1.get(0)
197+
point12 = part1.get(1)
198+
point21 = part2.get(0)
199+
point22 = part2.get(1)
200+
if abs(point11.x - -19926188.85) > 1e-2:
201+
print(point11.x, point12.x, point21.x, point22.x)
202+
pmstestlib.post_reason('did not get expected coordinates')
203+
return 'fail'
204+
if abs(point12.x - -20037508.34) > 1e-2:
205+
print(point11.x, point12.x, point21.x, point22.x)
206+
pmstestlib.post_reason('did not get expected coordinates')
207+
return 'fail'
208+
if abs(point21.x - 20037508.34) > 1e-2:
209+
print(point11.x, point12.x, point21.x, point22.x)
210+
pmstestlib.post_reason('did not get expected coordinates')
211+
return 'fail'
212+
if abs(point22.x - 19926188.85) > 1e-2:
213+
print(point11.x, point12.x, point21.x, point22.x)
214+
pmstestlib.post_reason('did not get expected coordinates')
215+
return 'fail'
216+
return 'success'
217+
126218
test_list = [
127219
bug_673,
128220
test_pattern,
221+
test_reprojection_lines_from_polar_stereographic_to_geographic,
222+
test_reprojection_lines_from_polar_stereographic_to_webmercator,
129223
None ]
130224

131225
if __name__ == '__main__':

0 commit comments

Comments
 (0)