Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Add from_proj4 function to create CRS from PROJ.4 definitions #1023

Closed
wants to merge 9 commits into from

Conversation

djhoese
Copy link

@djhoese djhoese commented Feb 14, 2018

Rationale

See #813 for more details. Many users and other software libraries use PROJ.4 definitions to define their data projections. To convert from these definitions to a cartopy CRS requires knowing the name of the CRS class and mapping PROJ.4 parameters to keyword arguments of the CRS class. This PR attempts to simplify this process by adding a from_proj4 function similar to the epsg function. The first commit of this PR only implements the functionality for the Stereographic and LambertConformal projections.

Implications

This PR separates out the PROJ.4 parsing code from the _EPSGProjection class and adds a _proj4.py python module to hold a new _PROJ4Projection class as a generic fallback class for any PROJ.4 strings that couldn't be mapped to a specific CRS class.

This PR also requires adding a new classmethod to all Projection subclasses called from_proj4.

Checklist

  • CRS's from_proj4 implemented and tests written
    • PlateCarree
    • TransverseMercator
    • OSGB
    • OSNI
    • UTM
    • EuroPP
    • Mercator
    • LambertCylindrical (Not Projection subclass)?
    • LambertConformal
    • LambertAzimuthalEqualArea
    • Miller
    • RotatedPole
    • Gnomonic
    • Stereographic
    • NorthPolarStereo
    • SouthPolarStereo
    • Orthographic
    • Mollweide
    • Robinson
    • InterruptedGoodeHomolosine
    • Geostationary
    • NearsidePerspective
    • AlbersEqualArea
    • AzimuthalEquidistant
    • Sinusoidal
  • Docstrings
  • Sphinx docs
  • Example:
    p_str1 = "+proj=stere +datum=WGS84 +lat_0=90 +lat_ts=45 +lon_0=-150"
    crs1 = ccrs.from_proj4(p_str1)

@SciTools-assistant SciTools-assistant added the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label Feb 14, 2018
def threshold(self):
x0, x1, y0, y1 = self.bounds
return min(x1 - x0, y1 - y0) / 100.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

W391 blank line at end of file

elif crs_class is None:
return _PROJ4Projection(proj4_dict, globe=globe, bounds=bounds)

return crs_class.from_proj4(proj4_dict)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

W292 no newline at end of file

@djhoese
Copy link
Author

djhoese commented Feb 15, 2018

@pelson CLA has been sent. Took me a bit to get used to all the code checks, but when you have time take a look and tell me what you hate.

Copy link
Member

@pelson pelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really great start. We need to flesh out the from_proj4 classmethod some more, but really appreciate you showing this early to help steer this in the right direction.

I'm not sure if I should show you this or not, but I went digging an found a commit way back when that attempted to convert cartopy<>proj4 strings. I didn't actually follow through with it but the diff can be seen at pelson/cartopy@b15391d...pelson:repr_from_proj4_init. Would be interested in getting your take on it, and welcome you to lift whatever you like from there into this (or subsequent) pull request.

class _PROJ4Projection(ccrs.Projection):
def __init__(self, proj4_terms, globe=None, bounds=None):
terms = get_proj4_dict(proj4_terms)
globe = _globe_from_proj4(terms) if globe is None else globe
Copy link
Member

@pelson pelson Feb 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to be careful in case there are globe terms in proj4 and a globe is provided.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good point. I'll fix that.

"""Create a `Globe` object from PROJ.4 parameters."""
globe_terms = filter(lambda term: term[0] in _GLOBE_PARAMS,
proj4_terms.items())
globe = ccrs.Globe(**{_GLOBE_PARAMS[name]: value for name, value in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style choice... I'd probably get rid of the filter:

globe = ccrs.Globe(**{_GLOBE_PARAMS[name]: value for name, value in proj4_terms.items()
                      if name in _GLOBE_PARAMS})

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was copied directly from the EPSG class ;)

other_terms.append(term)
super(_PROJ4Projection, self).__init__(other_terms, globe)

# FIXME: Can we guess at the bounds if not provided? Maybe transform
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we can do some automatic bounds calculation IFF the target projection doesn't define them itself.
I wouldn't want some projections to be allowed to set its own bounds though (e.g. PlateCarree).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this how EPSG works? Isn't this also what a lot of the CRS classes already do? They limit or calculate what the bounds are inside the CRS class?

terms = get_proj4_dict(proj4_terms)
globe = _globe_from_proj4(terms) if globe is None else globe

other_terms = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call these projection_terms.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Most of this was copied from the EPSG code.


other_terms = []
for term in terms.items():
if term[0] not in _GLOBE_PARAMS:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to allow globe_from_proj4 to manipulate the terms (and it can therefore remove the globe ones).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, especially when you consider the comment above about filtering out global parameters.

@@ -102,6 +102,11 @@ class Projection(six.with_metaclass(ABCMeta, CRS)):
'MultiPolygon': '_project_multipolygon',
}

@classmethod
def from_proj4(cls, proj4_dict, **kwargs):
raise NotImplementedError("This projection can not be created from a "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can be friendly by replacing This projection with the cls.__name__.

if 'x_0' in proj4_dict:
p_kwargs['false_easting'] = float(proj4_dict['x_0'])
if 'y_0' in proj4_dict:
p_kwargs['false_northing'] = float(proj4_dict['y_0'])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is repeated handling of things like false_northing happening. Can we factorise this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that, but for this initial commit I didn't do it because it felt like I would be trying to hard to reduce duplicate code and I wasn't sure if all CRS classes supported the false easting and northing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all projections do support false_easting / false_northing, but those that do all map to y_0. In some respects, if the proj4_terms includes this, then whether or not the projection supports it I'd be tempted to try passing the keyword on to the __init__ anyway. At least then the user gets a message about false_northing not being a valid keyword.

What would be missing from this approach is the user knowing that y_0 was the term that mapped to false_northing. Perhaps we could make use of inspect to determine ahead-of-time what the __init__ supports?

crs_class = proj_to_crs.get(proj)

# special cases
if proj == 'stere' and float(proj4_dict['lat_0']) < 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we push that into the Stereographic class?

raise ValueError("'bounds' must be specified for unknown CRS "
"'{}'".format(proj))
elif crs_class is None:
return _PROJ4Projection(proj4_dict, globe=globe, bounds=bounds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm 👎 for creating a projection that cartopy hasn't implemented. (i.e. returning a generic _PROJ4Projection instance)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're going to disagree on this, but in the end it is your call. You mentioned in the issue that cartopy doesn't follow PROJ.4 exactly (not a 1:1 mapping) because some scientific fields have existing expectations about how to describe a CRS. In a similar line of thinking I would argue that some fields use PROJ.4 to describe everything so limiting a user to projections that have a custom CRS class seems odd. There will be projections that are available that cartopy doesn't support, if all of the abstract methods are implemented shouldn't a user be able to use whatever projection they want? If not, why support EPSG at all?

I understand wanting to have a nice CRS class name and everything, but if a PROJ.4 generic fallback class is available and the functionality is the same then does it really matter? I guess it would effectively stop development on new CRS classes for cartopy though. </rant>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should also mentioned that I'm the maintainer of https://github.com/pytroll/satpy and https://github.com/pytroll/pyresample. The reason I wanted to add this functionality was to make it easier for our users to take our AreaDefinition (PROJ.4 projection + bounds + number of pixels) and create a cartopy plot from them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also if this doesn't get added to cartopy I will likely just add it to pyresample.



class _EPSGProjection(ccrs.Projection):
class _EPSGProjection(_PROJ4Projection):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems highly likely that we will remove _EPSGProjection and replace it with a function that returns an appropriate proj4 string > a real cartopy.crc.CRS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear: nothing for you to do here, just sharing my thoughts.

from cartopy._proj4 import get_proj4_dict, _PROJ4Projection
proj4_dict = get_proj4_dict(proj4_terms)

proj_to_crs = {
Copy link
Member

@pelson pelson Feb 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally I'd like this to be derived from the projection classes, not a static dictionary, if possible.

My branch does this at pelson/cartopy@b15391d...pelson:repr_from_proj4_init#diff-2af330a440b389503b4c32a5fbd26471R54 with:


+    def all_subclasses(cls):
+        return cls.__subclasses__() + [g for s in cls.__subclasses__()
+                                       for g in all_subclasses(s)]
+
+    potentials = []
+    for cls in all_subclasses(CRS):
+        if getattr(cls, '_proj4_proj', None) == proj:
+            potentials.append(cls)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can do this. I wasn't sure how "hacky" you wanted to get. ;)

@@ -2006,3 +2066,28 @@ def epsg(code):
"""
import cartopy._epsg
return cartopy._epsg._EPSGProjection(code)


def from_proj4(proj4_terms, globe=None, bounds=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put the implementation in the _proj4 module and make this a stub function that calls the appropriate function there (crs.py is already waaaay too long 😄).

@pelson pelson changed the title Add from_proj4 function to create CRS from PROJ.4 definitions WIP: Add from_proj4 function to create CRS from PROJ.4 definitions Feb 15, 2018
@SciTools-assistant SciTools-assistant added the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label Feb 15, 2018
@SciTools-assistant SciTools-assistant added Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form labels Feb 15, 2018
@pelson pelson self-assigned this Feb 15, 2018
@SciTools-assistant SciTools-assistant added the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label Feb 15, 2018
@fmaussion
Copy link

Thanks a lot! Looking forward to use this ;)

Is this going to work with EPSG projection IDs as well?

@djhoese
Copy link
Author

djhoese commented Feb 15, 2018

@fmaussion Right now, this PR probably won't handle this. As @pelson mentioned the epsg function (that already exists) will probably be rewritten to use from_proj4.

@SciTools-assistant SciTools-assistant added the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label Feb 16, 2018
@djhoese
Copy link
Author

djhoese commented Feb 16, 2018

@pelson I've done just about every change suggested. Let me know what you think. It seems overall better and somehow worse than before.

@djhoese
Copy link
Author

djhoese commented Feb 21, 2018

@pelson Do you think I should continue implementing the rest of the projections or is there something that still feels off to you about how I implemented this?

Copy link
Member

@pelson pelson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think I should continue implementing the rest of the projections or is there something that still feels off to you about how I implemented this?

Yes, I think you are heading in the right direction. Wherever possible I'd like to keep the proj4 detail to a minimum in crs.py and the CRS classes in general. That means putting much of the mapping from proj4 terms to CRS terms in _proj4.py. The example we have discussed is false_northing (but almost all other terms apply too) - ideally each CRS should not be implementing that mapping itself, but is should have the power to override the mapping if it really must.

I'm really excited about moving this forwards upon my return ( 🛫 🌞 🌴 🍸 🛬 ).

Thanks again for moving it forwards!

def __init__(self, proj4_terms, globe=None, bounds=None):
terms = get_proj4_dict(proj4_terms)
projection_terms, globe_terms = _split_globe_parameters(terms)
if globe is None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to raise an exception here is globe is not None and globe_terms is not empty.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the base CRS class which uses Globe() as its default. I can add an exception, but with this default in mind, does that change your opinion?

return globe


class _PROJ4Projection(ccrs.Projection):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm 👎 on this class existing altogether. (much as I am the EPSG class that already does).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well right now the EPSG class is based on this class. So either I revert the EPSG class and remove this or I leave this in. I was thinking this could be made public even. It doesn't need to be documented even, but if you want the functionality removed then ok.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI I've added this class to pyresample to convert pyresamples AreaDefinitions to CRS objects: pytroll/pyresample#102

return min(x1 - x0, y1 - y0) / 100.


def all_subclasses(cls):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may as well be private I think.

for g in all_subclasses(s)]


def from_proj4(proj4_terms, globe=None, bounds=None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a situation where we need to pass globe through here? It adds redundancy that we need to check against if it remains.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. Now that the default isn't to use the _Proj4Projection class as a last resort both of these keywords don't need to be here.

def from_proj4(proj4_terms, globe=None, bounds=None):
proj4_dict = get_proj4_dict(proj4_terms)

if not PROJ_TO_CRS:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PROJ_TO_CRS definitely needs a docstring - it wasn't clear to me that it was a mapping of the proj4 proj term to coordinate reference system.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regards to the case of projection specialisation, do you have an idea in mind for handling the case where multiple CRS classes implement the same projection. This is true of cases such as NorthPolarStereographic (https://github.com/SciTools/cartopy/blob/master/lib/cartopy/crs.py#L1376).

There is potentially a case to be made that such "projections" shouldn't exist at all - they are perhaps better modelled as instances of their base projections...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'll find out when I get there. I already have it implemented for Stereographic where the North and South cases are handled inside the class method.

if cls_proj is not None and cls_proj not in PROJ_TO_CRS:
PROJ_TO_CRS[cls_proj] = crs_class

proj = proj4_dict['proj']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want to be slightly more defensive here - what if proj isn't in the dict.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then you get a KeyError because your PROJ.4 string doesn't make sense...but ok.

@@ -1134,6 +1141,35 @@ def __init__(self, central_longitude=-96.0, central_latitude=39.0,
self._x_limits = bounds[0], bounds[2]
self._y_limits = bounds[1], bounds[3]

@classmethod
def from_proj4(cls, proj4_dict, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd want to assert that the proj parameter is set correctly (in this case, lcc).

if 'x_0' in proj4_dict:
p_kwargs['false_easting'] = float(proj4_dict['x_0'])
if 'y_0' in proj4_dict:
p_kwargs['false_northing'] = float(proj4_dict['y_0'])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all projections do support false_easting / false_northing, but those that do all map to y_0. In some respects, if the proj4_terms includes this, then whether or not the projection supports it I'd be tempted to try passing the keyword on to the __init__ anyway. At least then the user gets a message about false_northing not being a valid keyword.

What would be missing from this approach is the user knowing that y_0 was the term that mapped to false_northing. Perhaps we could make use of inspect to determine ahead-of-time what the __init__ supports?

@SciTools-assistant SciTools-assistant removed the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label Feb 23, 2018
@djhoese
Copy link
Author

djhoese commented Feb 23, 2018

@pelson I think I see what you are saying now. Maybe the _proj4.py module should have a generic "proj parameter" -> "cartopy crs keyword". To make it so individual classes can override that I suppose it would have to be a Projection.from_proj class method that calls the _proj4.py functions necessary to get the keyword arguments from the PROJ.4. So:

  1. User calls from_proj
  2. from_proj gets proj4_dict, gets appropriate CRS class, calls cls.from_proj(proj4_dict).
  3. Project.from_proj(...) calls _proj4.proj4_to_keywords (or something), returns class instance.

@knedlsepp
Copy link

I'd love to move this forward somehow. What are the next steps? Any help needed?

@djhoese
Copy link
Author

djhoese commented May 7, 2018

@knedlsepp I think I need @pelson to look at this again and decide what the best interface is for this and re-review what I have already. After that I can fix up anything that needs fixing (merge conflicts, etc).

@SciTools-assistant SciTools-assistant added the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label May 7, 2018
@djhoese
Copy link
Author

djhoese commented May 7, 2018

Ugh and I changed my username from davidh-ssec to djhoese. Looks like the CLA checker can't handle that situation.

@djhoese
Copy link
Author

djhoese commented Oct 10, 2018

@pelson Do you need me to do anything special to get the CLA checker fixed? Where would you like this PR to go from here?

@kaedonkers
Copy link
Member

@djhoese I have updated the contributors .json with your new username 👍

@knedlsepp I think I need @pelson to look at this again and decide what the best interface is for this and re-review what I have already. After that I can fix up anything that needs fixing (merge conflicts, etc).

@pelson Could you review the code again please?

@kaedonkers kaedonkers removed the Blocked: CLA needed See https://scitools.org.uk. Submit the form at: https://scitools.org.uk/cla/v4/form label Nov 5, 2018
@pelson
Copy link
Member

pelson commented Nov 12, 2018

I've found it really hard to get the bandwidth to do much on this recently, so apologies for the silence.
Having read this through again, I'm happy with the direction that has been taken on the whole. Some of the detail that I'd love to see though:

  • Projections have full control to handle the proj>instance transformation, and do not need to be implemented in cartopy core to be considered for the transformation (priority given to cartopy implementations though)
  • the transformation can handle the fact that multiple Projection classes can represent the same proj4 projection (e.g. NorthPolarStereographic and Stereographic)
  • the transformation should be conservative of the parameters passed to it - so some Projection classes will raise exceptions if they can't deal with certain parameters
  • there is no need for a Proj4Projection - it is entirely superseded by the proj4<>Projection class transformation, and can therefore be a Projection factory function (I support cartopy.crs.Projection.from_proj4(str_or_dict))
  • it would be useful / interesting to find out if we can fully implement __repr__ of a Projection based on this code. Currently, we don't have a real __repr__ as we don't store all non-proj4 metadata (to avoid redundant information), but this code allows us to go from proj4_string to cartopy class and keyword arguments

@djhoese
Copy link
Author

djhoese commented Nov 13, 2018

@pelson Maybe it has been too long for me, but I think most of these things are already handled as you want them. I'm not sure if you are saying you like the way they are done or want them changed.

Projections have full control to handle the proj>instance transformation, and do not need to be implemented in cartopy core to be considered for the transformation (priority given to cartopy implementations though)

Are you saying you want the Projection subclasses to handle all of the transform? I have implemented a from_proj4 already where each subclass implements their own transform handling. Everything except creating a Globe instance which is optional, but is also handled by the _proj4.py:from_proj4 method. What do you mean by "not need to be implemented in cartopy core to be considered"? You mean like third-party packages providing projection classes? Or do you mean something like a catch-all Projection class inside cartopy?

the transformation can handle the fact that multiple Projection classes can represent the same proj4 projection (e.g. NorthPolarStereographic and Stereographic)

This is already handled by special handling in the base Stereographic class.

the transformation should be conservative of the parameters passed to it - so some Projection classes will raise exceptions if they can't deal with certain parameters

So if any proj parameters are provided that cartopy doesn't know how to handle then an exception is raised? To be clear, exception over warning or something similar?

there is no need for a Proj4Projection - it is entirely superseded by the proj4<>Projection class transformation, and can therefore be a Projection factory function (I support cartopy.crs.Projection.from_proj4(str_or_dict))

The class needs to exist to support the _EPSGProjection class (it is the parent class). If you remove the EPSG class then this could likely be removed. As for the factor function, there is currently _proj4.py:from_proj4. I can't make Projection.from_proj4 work right now as that is the name of the classmethod for each Projection subclass that fullfills your first bullet point (classes have full control over their transformation). Right now Projection.from_proj4 raises a NotImplementedError to act as a abstract method that needs to be reimplemented by subclasses.

it would be useful / interesting to find out if we can fully implement repr of a Projection based on this code. Currently, we don't have a real repr as we don't store all non-proj4 metadata (to avoid redundant information), but this code allows us to go from proj4_string to cartopy class and keyword arguments

I'm not sure I understand. What type of __repr__ are you hoping to get? You would like a PROJ.4-like representation?

@djhoese
Copy link
Author

djhoese commented Mar 15, 2019

Latest commit is just a copy from the pyresample project where a contributor brought up a specific issue (see PR and related issue): pytroll/pyresample#161

@knutfrode
Copy link

This would be a highly welcome functionality. Is there a plan to add this soon?

@djhoese
Copy link
Author

djhoese commented Dec 9, 2019

I think this is just waiting on feedback from the maintainers. That said, at the last SciPy conference @dopplershift, @snowman2, and I talked about perhaps making cartopy more directly compatible with pyproj's CRS objects. Maybe even base cartopy's CRS objects off of pyproj's. Doing that would make this PR unnecessary in the best way.

@snowman2 Any further ideas on that?

@snowman2
Copy link
Contributor

snowman2 commented Dec 9, 2019

Any further ideas on that?

I think it would mostly entail using the pyproj.CRS and pyproj.Transformer classes to attain the same functionality already existing in cartopy. I think potentially adding CRS classes to pyproj that can be initialized with some projection parameters would be helpful as well.

@dopplershift
Copy link
Contributor

I'm interested in moving to complete reliance on PyProj and eliminating CartoPy's code that links to libproj. It would help simplify many things. I think that needs to happen for 0.19, though, since I'd like to get 0.18 out just as soon as I can clear the cycles.

@djhoese
Copy link
Author

djhoese commented Aug 31, 2021

I think now that #1808 has been merged this PR can be closed. My understanding is that we can do cartopy.CRS(pyproj.CRS(...)). This moves all of the PROJ parameter handling to pyproj and cartopy's own CRS object can access that when it needs to.

I'll let one of the maintainers close this. @snowman2 please correct me if I'm wrong.

@snowman2
Copy link
Contributor

My understanding is that we can do cartopy.CRS(pyproj.CRS(...))

That is correct. It is important to note that x_limits|y_limits only work if the input pyproj.CRS has a valid area of use and you do cartopy.crs.Projection(pyproj.CRS(...)). If you build a pyproj.CRS from a PROJ string, that information won't be there.

@djhoese
Copy link
Author

djhoese commented Aug 31, 2021

In pyresample we have "AreaDefinition" objects which are essentially a pyproj CRS + extents + size. Is it possible to create a cartopy CRS with a pyproj CRS and the x/y limits?

@snowman2
Copy link
Contributor

Is it possible to create a cartopy CRS with a pyproj CRS and the x/y limits?

The area of use can be set with a spatial reference code, WKT, or PROJ JSON.

I think this would be the simplest way to support custom CRS with an area of use:
https://pyproj4.github.io/pyproj/stable/build_crs.html

However, pyproj will need to be updated to pass area of use to the PROJ JSON.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants