2006-10-17 19:26:35 +02:00
/*****************************************************************************
2012-02-02 02:53:35 +01:00
*
2006-10-17 19:26:35 +02:00
* This file is part of Mapnik ( c + + mapping toolkit )
*
2017-05-05 13:02:01 +02:00
* Copyright ( C ) 2017 Artem Pavlenko
2006-10-17 19:26:35 +02:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2008-02-04 12:54:07 +01:00
// mapnik
2011-10-19 16:29:24 +02:00
# include <mapnik/global.hpp>
2017-03-27 17:14:51 +02:00
# include <mapnik/geometry/box2d.hpp>
2014-07-23 07:40:39 +02:00
# include <mapnik/projection.hpp>
2006-10-17 19:26:35 +02:00
# include <mapnik/proj_transform.hpp>
2012-02-02 02:53:35 +01:00
# include <mapnik/coord.hpp>
2016-04-18 11:16:32 +02:00
# include <mapnik/util/is_clockwise.hpp>
2011-06-01 00:38:15 +02:00
2013-01-28 19:45:41 +01:00
# ifdef MAPNIK_USE_PROJ4
2008-02-04 12:54:07 +01:00
// proj4
2007-10-08 19:42:41 +02:00
# include <proj_api.h>
2013-01-28 19:45:41 +01:00
# endif
2007-10-08 19:42:41 +02:00
2011-06-01 00:38:15 +02:00
// stl
# include <vector>
2013-06-03 05:19:33 +02:00
# include <stdexcept>
2011-06-01 00:38:15 +02:00
2006-10-17 19:26:35 +02:00
namespace mapnik {
2012-02-02 02:53:35 +01:00
proj_transform : : proj_transform ( projection const & source ,
2010-06-02 13:03:30 +02:00
projection const & dest )
: source_ ( source ) ,
2013-01-28 07:47:32 +01:00
dest_ ( dest ) ,
is_source_longlat_ ( false ) ,
is_dest_longlat_ ( false ) ,
2014-10-23 00:34:17 +02:00
is_source_equal_dest_ ( false ) ,
2013-01-28 07:47:32 +01:00
wgs84_to_merc_ ( false ) ,
2014-10-23 00:34:17 +02:00
merc_to_wgs84_ ( false )
2010-06-02 13:03:30 +02:00
{
2010-07-22 01:05:22 +02:00
is_source_equal_dest_ = ( source_ = = dest_ ) ;
2013-01-28 07:47:32 +01:00
if ( ! is_source_equal_dest_ )
2011-09-12 04:10:58 +02:00
{
2013-01-28 07:47:32 +01:00
is_source_longlat_ = source_ . is_geographic ( ) ;
is_dest_longlat_ = dest_ . is_geographic ( ) ;
boost : : optional < well_known_srs_e > src_k = source . well_known ( ) ;
boost : : optional < well_known_srs_e > dest_k = dest . well_known ( ) ;
2013-01-28 21:10:24 +01:00
bool known_trans = false ;
2013-01-28 07:47:32 +01:00
if ( src_k & & dest_k )
{
2013-01-28 21:10:24 +01:00
if ( * src_k = = WGS_84 & & * dest_k = = G_MERC )
{
wgs84_to_merc_ = true ;
known_trans = true ;
}
else if ( * src_k = = G_MERC & & * dest_k = = WGS_84 )
{
merc_to_wgs84_ = true ;
known_trans = true ;
}
2013-01-28 07:47:32 +01:00
}
2013-01-28 21:10:24 +01:00
if ( ! known_trans )
2013-01-28 07:47:32 +01:00
{
2013-01-28 20:03:07 +01:00
# ifdef MAPNIK_USE_PROJ4
2013-01-28 07:47:32 +01:00
source_ . init_proj4 ( ) ;
dest_ . init_proj4 ( ) ;
2013-01-28 20:03:07 +01:00
# else
throw std : : runtime_error ( std : : string ( " Cannot initialize proj_transform for given projections without proj4 support (-DMAPNIK_USE_PROJ4): ' " ) + source_ . params ( ) + " '->' " + dest_ . params ( ) + " ' " ) ;
# endif
2013-01-28 07:47:32 +01:00
}
2011-09-12 04:10:58 +02:00
}
2010-06-02 13:03:30 +02:00
}
2010-07-22 01:05:22 +02:00
bool proj_transform : : equal ( ) const
{
return is_source_equal_dest_ ;
}
2015-04-01 23:18:05 +02:00
bool proj_transform : : is_known ( ) const
{
return merc_to_wgs84_ | | wgs84_to_merc_ ;
}
2011-09-13 17:41:39 +02:00
bool proj_transform : : forward ( double & x , double & y , double & z ) const
{
return forward ( & x , & y , & z , 1 ) ;
}
2015-04-09 22:22:51 +02:00
bool proj_transform : : forward ( geometry : : point < double > & p ) const
2015-04-01 23:18:05 +02:00
{
double z = 0 ;
return forward ( & ( p . x ) , & ( p . y ) , & z , 1 ) ;
}
2016-04-12 10:12:16 +02:00
unsigned int proj_transform : : forward ( std : : vector < geometry : : point < double > > & ls ) const
2015-04-01 23:18:05 +02:00
{
2015-04-10 21:05:58 +02:00
std : : size_t size = ls . size ( ) ;
if ( size = = 0 ) return 0 ;
2015-04-01 23:18:05 +02:00
if ( is_source_equal_dest_ )
return 0 ;
if ( wgs84_to_merc_ )
{
lonlat2merc ( ls ) ;
return 0 ;
}
else if ( merc_to_wgs84_ )
{
merc2lonlat ( ls ) ;
return 0 ;
}
2015-04-10 21:05:58 +02:00
geometry : : point < double > * ptr = ls . data ( ) ;
double * x = reinterpret_cast < double * > ( ptr ) ;
double * y = x + 1 ;
2016-04-05 21:27:32 +02:00
double * z = nullptr ;
2015-04-10 21:05:58 +02:00
if ( ! forward ( x , y , z , size , 2 ) )
2015-04-01 23:18:05 +02:00
{
2015-04-10 21:05:58 +02:00
return size ;
2015-04-01 23:18:05 +02:00
}
2015-04-10 21:05:58 +02:00
return 0 ;
2015-04-01 23:18:05 +02:00
}
2015-04-10 21:05:58 +02:00
bool proj_transform : : forward ( double * x , double * y , double * z , int point_count , int offset ) const
2010-06-02 13:03:30 +02:00
{
2011-09-12 04:10:58 +02:00
2010-07-22 01:05:22 +02:00
if ( is_source_equal_dest_ )
2010-06-02 13:03:30 +02:00
return true ;
2008-12-07 17:23:57 +01:00
2013-01-28 07:47:32 +01:00
if ( wgs84_to_merc_ )
{
return lonlat2merc ( x , y , point_count ) ;
}
else if ( merc_to_wgs84_ )
{
return merc2lonlat ( x , y , point_count ) ;
2011-09-12 04:10:58 +02:00
}
2012-02-02 02:53:35 +01:00
2013-01-28 19:45:41 +01:00
# ifdef MAPNIK_USE_PROJ4
2012-02-02 02:53:35 +01:00
if ( is_source_longlat_ )
2010-06-02 13:03:30 +02:00
{
2011-09-12 20:30:34 +02:00
int i ;
for ( i = 0 ; i < point_count ; i + + ) {
2015-04-10 21:05:58 +02:00
x [ i * offset ] * = DEG_TO_RAD ;
y [ i * offset ] * = DEG_TO_RAD ;
2011-09-12 20:30:34 +02:00
}
2010-06-02 13:03:30 +02:00
}
2010-07-22 01:05:22 +02:00
2013-01-29 09:36:38 +01:00
if ( pj_transform ( source_ . proj_ , dest_ . proj_ , point_count ,
2015-04-10 21:05:58 +02:00
offset , x , y , z ) ! = 0 )
2013-01-28 19:45:41 +01:00
{
2013-01-29 09:36:38 +01:00
return false ;
2013-01-28 19:45:41 +01:00
}
2011-06-01 00:38:15 +02:00
2015-06-11 03:34:11 +02:00
for ( int j = 0 ; j < point_count ; j + + ) {
if ( x [ j ] = = HUGE_VAL | | y [ j ] = = HUGE_VAL )
{
return false ;
}
}
2010-08-10 20:18:31 +02:00
if ( is_dest_longlat_ )
2010-06-02 13:03:30 +02:00
{
2011-09-13 17:41:39 +02:00
int i ;
2011-09-12 20:30:34 +02:00
for ( i = 0 ; i < point_count ; i + + ) {
2015-04-10 21:05:58 +02:00
x [ i * offset ] * = RAD_TO_DEG ;
y [ i * offset ] * = RAD_TO_DEG ;
2011-09-12 20:30:34 +02:00
}
2010-06-02 13:03:30 +02:00
}
2013-01-28 19:45:41 +01:00
# endif
2010-06-02 13:03:30 +02:00
return true ;
2011-09-12 20:30:34 +02:00
}
2015-04-10 21:05:58 +02:00
bool proj_transform : : backward ( double * x , double * y , double * z , int point_count , int offset ) const
2010-06-02 13:03:30 +02:00
{
2010-07-22 01:05:22 +02:00
if ( is_source_equal_dest_ )
2006-10-17 19:26:35 +02:00
return true ;
2011-06-01 00:38:15 +02:00
2013-01-28 07:47:32 +01:00
if ( wgs84_to_merc_ )
{
return merc2lonlat ( x , y , point_count ) ;
}
else if ( merc_to_wgs84_ )
{
return lonlat2merc ( x , y , point_count ) ;
2011-09-12 04:10:58 +02:00
}
2013-01-28 19:45:41 +01:00
# ifdef MAPNIK_USE_PROJ4
2010-08-10 20:18:31 +02:00
if ( is_dest_longlat_ )
2010-06-02 13:03:30 +02:00
{
2016-05-26 11:36:55 +02:00
for ( int i = 0 ; i < point_count ; + + i )
{
x [ i * offset ] * = DEG_TO_RAD ;
y [ i * offset ] * = DEG_TO_RAD ;
2011-09-12 20:30:34 +02:00
}
2009-05-24 08:31:32 +02:00
}
2011-06-01 00:38:15 +02:00
2016-05-26 11:36:55 +02:00
if ( pj_transform ( dest_ . proj_ , source_ . proj_ , point_count ,
offset , x , y , z ) ! = 0 )
2012-09-19 14:51:53 +02:00
{
2013-01-29 09:36:38 +01:00
return false ;
2012-09-19 14:51:53 +02:00
}
2011-06-01 00:38:15 +02:00
2016-05-26 11:36:55 +02:00
for ( int j = 0 ; j < point_count ; + + j )
{
2015-06-11 03:34:11 +02:00
if ( x [ j ] = = HUGE_VAL | | y [ j ] = = HUGE_VAL )
{
return false ;
}
}
2010-08-10 20:18:31 +02:00
if ( is_source_longlat_ )
2009-05-24 08:31:32 +02:00
{
2016-05-26 11:36:55 +02:00
for ( int i = 0 ; i < point_count ; + + i )
{
x [ i * offset ] * = RAD_TO_DEG ;
y [ i * offset ] * = RAD_TO_DEG ;
2011-09-12 20:30:34 +02:00
}
2009-05-24 08:31:32 +02:00
}
2013-01-28 19:45:41 +01:00
# endif
2010-06-02 13:03:30 +02:00
return true ;
}
2011-09-12 20:30:34 +02:00
bool proj_transform : : backward ( double & x , double & y , double & z ) const
{
return backward ( & x , & y , & z , 1 ) ;
}
2015-04-09 22:22:51 +02:00
bool proj_transform : : backward ( geometry : : point < double > & p ) const
2015-04-01 23:18:05 +02:00
{
double z = 0 ;
return backward ( & ( p . x ) , & ( p . y ) , & z , 1 ) ;
}
2016-04-12 10:12:16 +02:00
unsigned int proj_transform : : backward ( std : : vector < geometry : : point < double > > & ls ) const
2015-04-01 23:18:05 +02:00
{
2015-04-10 21:05:58 +02:00
std : : size_t size = ls . size ( ) ;
if ( size = = 0 ) return 0 ;
2015-06-02 12:10:41 +02:00
2015-04-01 23:18:05 +02:00
if ( is_source_equal_dest_ )
return 0 ;
if ( wgs84_to_merc_ )
{
merc2lonlat ( ls ) ;
return 0 ;
}
else if ( merc_to_wgs84_ )
{
lonlat2merc ( ls ) ;
return 0 ;
}
2015-06-02 12:10:41 +02:00
2015-04-10 21:05:58 +02:00
geometry : : point < double > * ptr = ls . data ( ) ;
double * x = reinterpret_cast < double * > ( ptr ) ;
double * y = x + 1 ;
2016-04-05 21:27:32 +02:00
double * z = nullptr ;
2016-05-26 11:36:55 +02:00
if ( ! backward ( x , y , z , size , 2 ) )
2015-04-01 23:18:05 +02:00
{
2015-04-10 21:05:58 +02:00
return size ;
2015-04-01 23:18:05 +02:00
}
2015-04-10 21:05:58 +02:00
return 0 ;
2015-04-01 23:18:05 +02:00
}
2011-09-12 20:30:34 +02:00
2011-04-13 21:40:44 +02:00
bool proj_transform : : forward ( box2d < double > & box ) const
{
if ( is_source_equal_dest_ )
return true ;
2011-06-01 00:38:15 +02:00
2015-05-15 20:21:51 +02:00
double llx = box . minx ( ) ;
double ulx = box . minx ( ) ;
double lly = box . miny ( ) ;
double lry = box . miny ( ) ;
double lrx = box . maxx ( ) ;
double urx = box . maxx ( ) ;
2015-06-02 12:10:41 +02:00
double uly = box . maxy ( ) ;
2015-05-15 20:21:51 +02:00
double ury = box . maxy ( ) ;
2011-04-13 21:40:44 +02:00
double z = 0.0 ;
2015-05-15 19:15:18 +02:00
if ( ! forward ( llx , lly , z ) )
2011-04-13 23:03:15 +02:00
return false ;
2015-05-15 19:15:18 +02:00
if ( ! forward ( lrx , lry , z ) )
2011-04-13 23:03:15 +02:00
return false ;
2015-05-15 19:15:18 +02:00
if ( ! forward ( ulx , uly , z ) )
return false ;
if ( ! forward ( urx , ury , z ) )
return false ;
2015-05-15 20:21:51 +02:00
double minx = std : : min ( ulx , llx ) ;
double miny = std : : min ( lly , lry ) ;
double maxx = std : : max ( urx , lrx ) ;
double maxy = std : : max ( ury , uly ) ;
box . init ( minx ,
miny ,
maxx ,
maxy ) ;
2011-04-13 23:03:15 +02:00
return true ;
2012-02-02 02:53:35 +01:00
}
2011-06-01 00:38:15 +02:00
2011-04-13 21:40:44 +02:00
bool proj_transform : : backward ( box2d < double > & box ) const
{
if ( is_source_equal_dest_ )
return true ;
2016-05-26 16:26:04 +02:00
double x [ 4 ] , y [ 4 ] ;
x [ 0 ] = box . minx ( ) ; // llx 0
y [ 0 ] = box . miny ( ) ; // lly 1
x [ 1 ] = box . maxx ( ) ; // lrx 2
y [ 1 ] = box . miny ( ) ; // lry 3
x [ 2 ] = box . minx ( ) ; // ulx 4
y [ 2 ] = box . maxy ( ) ; // uly 5
x [ 3 ] = box . maxx ( ) ; // urx 6
y [ 3 ] = box . maxy ( ) ; // ury 7
if ( ! backward ( x , y , nullptr , 4 , 1 ) )
2015-05-15 19:15:18 +02:00
return false ;
2016-05-26 16:26:04 +02:00
double minx = std : : min ( x [ 0 ] , x [ 2 ] ) ;
double miny = std : : min ( y [ 0 ] , y [ 1 ] ) ;
double maxx = std : : max ( x [ 1 ] , x [ 3 ] ) ;
double maxy = std : : max ( y [ 2 ] , y [ 3 ] ) ;
2016-05-26 11:36:55 +02:00
box . init ( minx , miny , maxx , maxy ) ;
2011-04-13 23:03:15 +02:00
return true ;
2011-04-13 21:40:44 +02:00
}
2015-06-02 12:10:41 +02:00
// Returns points in clockwise order. This allows us to do anti-meridian checks.
2011-06-01 00:38:15 +02:00
void envelope_points ( std : : vector < coord < double , 2 > > & coords , box2d < double > & env , int points )
{
double width = env . width ( ) ;
double height = env . height ( ) ;
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
int steps ;
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
if ( points < = 4 ) {
steps = 0 ;
} else {
2013-01-17 22:53:48 +01:00
steps = static_cast < int > ( std : : ceil ( ( points - 4 ) / 4.0 ) ) ;
2011-06-01 00:38:15 +02:00
}
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
steps + = 1 ;
double xstep = width / steps ;
double ystep = height / steps ;
2012-02-02 02:53:35 +01:00
2015-01-27 05:15:14 +01:00
coords . resize ( points ) ;
for ( int i = 0 ; i < steps ; i + + ) {
// top: left>right
coords [ i ] = coord < double , 2 > ( env . minx ( ) + i * xstep , env . maxy ( ) ) ;
// right: top>bottom
coords [ i + steps ] = coord < double , 2 > ( env . maxx ( ) , env . maxy ( ) - i * ystep ) ;
// bottom: right>left
coords [ i + steps * 2 ] = coord < double , 2 > ( env . maxx ( ) - i * xstep , env . miny ( ) ) ;
// left: bottom>top
coords [ i + steps * 3 ] = coord < double , 2 > ( env . minx ( ) , env . miny ( ) + i * ystep ) ;
2011-06-01 00:38:15 +02:00
}
2015-01-27 05:15:14 +01:00
}
2011-06-01 00:38:15 +02:00
box2d < double > calculate_bbox ( std : : vector < coord < double , 2 > > & points ) {
std : : vector < coord < double , 2 > > : : iterator it = points . begin ( ) ;
std : : vector < coord < double , 2 > > : : iterator it_end = points . end ( ) ;
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
box2d < double > env ( * it , * ( + + it ) ) ;
for ( ; it ! = it_end ; + + it ) {
env . expand_to_include ( * it ) ;
}
return env ;
}
2015-06-02 12:10:41 +02:00
// More robust, but expensive, bbox transform
// in the face of proj4 out of bounds conditions.
// Can result in 20 -> 10 r/s performance hit.
// Alternative is to provide proper clipping box
// in the target srs by setting map 'maximum-extent'
2011-06-01 00:38:15 +02:00
bool proj_transform : : backward ( box2d < double > & env , int points ) const
{
if ( is_source_equal_dest_ )
return true ;
2012-02-02 02:53:35 +01:00
2014-08-20 23:47:27 +02:00
if ( wgs84_to_merc_ | | merc_to_wgs84_ )
{
return backward ( env ) ;
}
2011-06-01 00:38:15 +02:00
std : : vector < coord < double , 2 > > coords ;
2015-01-27 05:15:14 +01:00
envelope_points ( coords , env , points ) ; // this is always clockwise
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
double z ;
for ( std : : vector < coord < double , 2 > > : : iterator it = coords . begin ( ) ; it ! = coords . end ( ) ; + + it ) {
z = 0 ;
if ( ! backward ( it - > x , it - > y , z ) ) {
return false ;
}
}
box2d < double > result = calculate_bbox ( coords ) ;
2016-04-18 11:16:32 +02:00
if ( is_source_longlat_ & & ! util : : is_clockwise ( coords ) )
2015-06-02 12:10:41 +02:00
{
// we've gone to a geographic CS, and our clockwise envelope has
// changed into an anticlockwise one. This means we've crossed the antimeridian, and
// need to expand the X direction to +/-180 to include all the data. Once we can deal
// with multiple bboxes in queries we can improve.
double miny = result . miny ( ) ;
result . expand_to_include ( - 180.0 , miny ) ;
result . expand_to_include ( 180.0 , miny ) ;
2015-01-27 05:15:14 +01:00
}
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
env . re_center ( result . center ( ) . x , result . center ( ) . y ) ;
env . height ( result . height ( ) ) ;
env . width ( result . width ( ) ) ;
return true ;
}
bool proj_transform : : forward ( box2d < double > & env , int points ) const
{
if ( is_source_equal_dest_ )
return true ;
2012-02-02 02:53:35 +01:00
2014-08-20 23:47:27 +02:00
if ( wgs84_to_merc_ | | merc_to_wgs84_ )
{
return forward ( env ) ;
}
2011-06-01 00:38:15 +02:00
std : : vector < coord < double , 2 > > coords ;
2015-01-27 05:15:14 +01:00
envelope_points ( coords , env , points ) ; // this is always clockwise
2012-02-02 02:53:35 +01:00
2011-06-01 00:38:15 +02:00
double z ;
for ( std : : vector < coord < double , 2 > > : : iterator it = coords . begin ( ) ; it ! = coords . end ( ) ; + + it ) {
z = 0 ;
if ( ! forward ( it - > x , it - > y , z ) ) {
return false ;
}
}
box2d < double > result = calculate_bbox ( coords ) ;
2012-02-02 02:53:35 +01:00
2016-04-18 11:16:32 +02:00
if ( is_dest_longlat_ & & ! util : : is_clockwise ( coords ) )
2015-06-02 12:10:41 +02:00
{
// we've gone to a geographic CS, and our clockwise envelope has
// changed into an anticlockwise one. This means we've crossed the antimeridian, and
// need to expand the X direction to +/-180 to include all the data. Once we can deal
// with multiple bboxes in queries we can improve.
double miny = result . miny ( ) ;
result . expand_to_include ( - 180.0 , miny ) ;
result . expand_to_include ( 180.0 , miny ) ;
2015-01-27 05:15:14 +01:00
}
2011-06-01 00:38:15 +02:00
env . re_center ( result . center ( ) . x , result . center ( ) . y ) ;
env . height ( result . height ( ) ) ;
env . width ( result . width ( ) ) ;
return true ;
}
2010-06-02 13:03:30 +02:00
mapnik : : projection const & proj_transform : : source ( ) const
{
return source_ ;
}
mapnik : : projection const & proj_transform : : dest ( ) const
{
return dest_ ;
}
2012-02-02 02:53:35 +01:00
2006-10-17 19:26:35 +02:00
}