def main(): parser = get_parser() args = parser.parse_args() grid_name = args.grid_name clon = args.center_longitude clat = args.center_latitude pixel_size_x = args.pixel_size_x pixel_size_y = args.pixel_size_y if pixel_size_y > 0 and not args.dont_touch_ysize: pixel_size_y *= -1 grid_width = args.grid_width grid_height = args.grid_height if clon < -180 or clon > 180: print "ERROR: Center longitude must be between -180 and 180 degrees" return -1 if clat < -90 or clat > 90: print "ERROR: Center latitude must be between -90 and 90 degrees" return -1 proj_str = determine_projection(clon, clat, args.proj_str) p = Proj(proj_str) origin_x = p(clon, clat)[0] + (grid_width / 2.0 * pixel_size_x * -1) origin_y = p(clon, clat)[1] + (grid_height / 2.0 * pixel_size_y * -1) if p.is_latlong(): # Origin is in degrees so we need to add a unit string to it origin_x = "%0.3fdeg" % origin_x origin_y = "%0.3fdeg" % origin_y else: origin_lon, origin_lat = p(origin_x, origin_y, inverse=True) origin_x = "%0.3fdeg" % origin_lon origin_y = "%0.3fdeg" % origin_lat valid_config_line = CONFIG_LINE_FORMAT % { "grid_name": grid_name, "proj4_str": proj_str, "origin_x": origin_x, "origin_y": origin_y, "pixel_size_x": pixel_size_x, "pixel_size_y": pixel_size_y, "width": grid_width, "height": grid_height, } print valid_config_line
def main(): parser = get_parser() args = parser.parse_args() grid_name = args.grid_name clon = args.center_longitude clat = args.center_latitude pixel_size_x = args.pixel_size_x pixel_size_y = args.pixel_size_y if pixel_size_y > 0 and not args.dont_touch_ysize: pixel_size_y *= -1 grid_width = args.grid_width grid_height = args.grid_height if clon < -180 or clon > 180: print("ERROR: Center longitude must be between -180 and 180 degrees") return -1 if clat < -90 or clat > 90: print("ERROR: Center latitude must be between -90 and 90 degrees") return -1 proj_str = determine_projection(clon, clat, args.proj_str) p = Proj(proj_str) origin_x = p(clon, clat)[0] + (grid_width / 2.0 * pixel_size_x * -1) origin_y = p(clon, clat)[1] + (grid_height / 2.0 * pixel_size_y * -1) if p.is_latlong(): # Origin is in degrees so we need to add a unit string to it origin_x = "%0.5fdeg" % origin_x origin_y = "%0.5fdeg" % origin_y else: origin_lon, origin_lat = p(origin_x, origin_y, inverse=True) origin_x = "%0.5fdeg" % origin_lon origin_y = "%0.5fdeg" % origin_lat valid_config_line = CONFIG_LINE_FORMAT % { "grid_name": grid_name, "proj4_str": proj_str, "origin_x": origin_x, "origin_y": origin_y, "pixel_size_x": pixel_size_x, "pixel_size_y": pixel_size_y, "width": grid_width, "height": grid_height, } print(valid_config_line)
def cell_width_meters(self): """Estimation of what a latlong cell width would be in meters. """ proj4_dict = self.proj4_dict lon0 = proj4_dict.get("lon0", 0.0) lat0 = proj4_dict.get("lat0", 0.0) p = Proj("+proj=eqc +lon0=%f +lat0=%f" % (lon0, lat0)) x0, y0 = p(lon0, lat0) x1, y1 = p(lon0 + self["cell_width"], lat0) return abs(x1 - x0)
def create_nav_binaries(lon_fn, lat_fn, proj4_str, width, height, origin_x, origin_y, x_psize, y_psize, psize_radians=False): """Create longitude and latitude binaries from the projection definition provided. """ p = Proj(proj4_str) # Open the files and a memory mapped array lon_file = numpy.memmap(lon_fn, dtype=numpy.float32, mode="w+", shape=(abs(height), abs(width))) lat_file = numpy.memmap(lat_fn, dtype=numpy.float32, mode="w+", shape=(abs(height), abs(width))) cols = numpy.arange(width) for row in range(height): grid_x = cols * x_psize + origin_x grid_y = numpy.repeat(row * y_psize + origin_y, width) if "longlat" in proj4_str and not psize_radians: lon_file[row] = grid_x[:] lat_file[row] = grid_y[:] else: lons, lats = p(grid_x, grid_y, inverse=True) lon_file[row] = lons[:] lat_file[row] = lats[:]
def proj(self): if self.p is None: self.p = Proj(self["proj4_definition"]) return self.p
def parse_proj4_config_line(grid_name, parts): """Return a dictionary of information for a specific PROJ.4 grid from a grid configuration line. ``parts`` should be every comma-separated part of the line including the ``grid_name``. """ info = {} proj4_str = parts[2] # Test to make sure the proj4_str is valid in pyproj's eyes try: p = Proj(proj4_str) del p except StandardError: LOG.error("Invalid proj4 string in '%s' : '%s'" % (grid_name, proj4_str)) raise # Some parts can be None, but not all try: static = True if parts[3] == "None" or parts[3] == "": static = False grid_width = None else: grid_width = int(parts[3]) if parts[4] == "None" or parts[4] == "": static = False grid_height = None else: grid_height = int(parts[4]) if parts[5] == "None" or parts[5] == "": LOG.warning("Grid '%s' may not process properly due to unspecified pixel size", grid_name) static = False pixel_size_x = None else: pixel_size_x = float(parts[5]) if parts[6] == "None" or parts[6] == "": LOG.warning("Grid '%s' may not process properly due to unspecified pixel size", grid_name) static = False pixel_size_y = None else: pixel_size_y = float(parts[6]) convert_xorigin_to_meters = False if parts[7] == "None" or parts[7] == "": static = False grid_origin_x = None else: grid_origin_x, convert_xorigin_to_meters = _parse_meter_degree_param(parts[7]) convert_yorigin_to_meters = False if parts[8] == "None" or parts[8] == "": static = False grid_origin_y = None else: grid_origin_y, convert_yorigin_to_meters = _parse_meter_degree_param(parts[8]) except StandardError: LOG.error("Could not parse proj4 grid configuration: '%s'" % (grid_name,)) raise if (pixel_size_x is None and pixel_size_y is not None) or (pixel_size_x is not None and pixel_size_y is None): LOG.error("Both or neither pixel sizes must be specified for '%s'" % grid_name) raise ValueError("Both or neither pixel sizes must be specified for '%s'" % grid_name) if (grid_width is None and grid_height is not None) or (grid_width is not None and grid_height is None): LOG.error("Both or neither grid sizes must be specified for '%s'" % grid_name) raise ValueError("Both or neither grid sizes must be specified for '%s'" % grid_name) if (grid_origin_x is None and grid_origin_y is not None) or (grid_origin_x is not None and grid_origin_y is None): LOG.error("Both or neither grid origins must be specified for '%s'" % grid_name) raise ValueError("Both or neither grid origins must be specified for '%s'" % grid_name) if grid_width is None and pixel_size_x is None: LOG.error("Either grid size or pixel size must be specified for '%s'" % grid_name) raise ValueError("Either grid size or pixel size must be specified for '%s'" % grid_name) if convert_xorigin_to_meters != convert_yorigin_to_meters: LOG.error("Grid origin parameters must be in the same units (meters vs degrees)") raise ValueError("Grid origin parameters must be in the same units (meters vs degrees)") # Convert any parameters from degrees to meters (we already made sure both need to be converted above) p = Proj(proj4_str) if convert_xorigin_to_meters and not p.is_latlong(): meters_x, meters_y = p(grid_origin_x, grid_origin_y) LOG.debug( "Converted grid '%s' origin from (lon: %f, lat: %f) to (x: %f, y: %f)", grid_name, grid_origin_x, grid_origin_y, meters_x, meters_y, ) grid_origin_x, grid_origin_y = meters_x, meters_y elif not convert_xorigin_to_meters and (grid_origin_x is not None and p.is_latlong()): LOG.error("Lat/Lon grid '%s' must have its origin in degrees", grid_name) raise ValueError("Lat/Lon grid '%s' must have its origin in degrees" % (grid_name,)) proj4_dict = get_proj4_info(proj4_str) info.update(**proj4_dict) info["grid_kind"] = "proj4" info["static"] = static info["proj4_str"] = proj4_str info["pixel_size_x"] = pixel_size_x info["pixel_size_y"] = pixel_size_y info["grid_origin_x"] = grid_origin_x info["grid_origin_y"] = grid_origin_y info["grid_width"] = grid_width info["grid_height"] = grid_height return info
def parse_proj4_config_line(grid_name, parts): """Return a dictionary of information for a specific PROJ.4 grid from a grid configuration line. ``parts`` should be every comma-separated part of the line including the ``grid_name``. """ info = {} proj4_str = parts[2] # Test to make sure the proj4_str is valid in pyproj's eyes try: p = Proj(proj4_str) del p except ValueError: LOG.error("Invalid proj4 string in '%s' : '%s'" % (grid_name, proj4_str)) raise # Some parts can be None, but not all try: static = True if parts[3] == "None" or parts[3] == '': static = False grid_width = None else: grid_width = int(parts[3]) if parts[4] == "None" or parts[4] == '': static = False grid_height = None else: grid_height = int(parts[4]) if parts[5] == "None" or parts[5] == '': LOG.warning("Grid '%s' may not process properly due to unspecified pixel size", grid_name) static = False pixel_size_x = None else: pixel_size_x = float(parts[5]) if parts[6] == "None" or parts[6] == '': LOG.warning("Grid '%s' may not process properly due to unspecified pixel size", grid_name) static = False pixel_size_y = None else: pixel_size_y = float(parts[6]) convert_xorigin_to_meters = False if parts[7] == "None" or parts[7] == '': static = False grid_origin_x = None else: grid_origin_x, convert_xorigin_to_meters = _parse_meter_degree_param(parts[7]) convert_yorigin_to_meters = False if parts[8] == "None" or parts[8] == '': static = False grid_origin_y = None else: grid_origin_y, convert_yorigin_to_meters = _parse_meter_degree_param(parts[8]) except ValueError: LOG.error("Could not parse proj4 grid configuration: '%s'" % (grid_name,)) raise if (pixel_size_x is None and pixel_size_y is not None) or \ (pixel_size_x is not None and pixel_size_y is None): LOG.error("Both or neither pixel sizes must be specified for '%s'" % grid_name) raise ValueError("Both or neither pixel sizes must be specified for '%s'" % grid_name) if (grid_width is None and grid_height is not None) or \ (grid_width is not None and grid_height is None): LOG.error("Both or neither grid sizes must be specified for '%s'" % grid_name) raise ValueError("Both or neither grid sizes must be specified for '%s'" % grid_name) if (grid_origin_x is None and grid_origin_y is not None) or \ (grid_origin_x is not None and grid_origin_y is None): LOG.error("Both or neither grid origins must be specified for '%s'" % grid_name) raise ValueError("Both or neither grid origins must be specified for '%s'" % grid_name) if grid_width is None and pixel_size_x is None: LOG.error("Either grid size or pixel size must be specified for '%s'" % grid_name) raise ValueError("Either grid size or pixel size must be specified for '%s'" % grid_name) if convert_xorigin_to_meters != convert_yorigin_to_meters: LOG.error("Grid origin parameters must be in the same units (meters vs degrees)") raise ValueError("Grid origin parameters must be in the same units (meters vs degrees)") # Convert any parameters from degrees to meters (we already made sure both need to be converted above) p = Proj(proj4_str) if convert_xorigin_to_meters and not p.is_latlong(): meters_x, meters_y = p(grid_origin_x, grid_origin_y) LOG.debug("Converted grid '%s' origin from (lon: %f, lat: %f) to (x: %f, y: %f)", grid_name, grid_origin_x, grid_origin_y, meters_x, meters_y) grid_origin_x, grid_origin_y = meters_x, meters_y elif not convert_xorigin_to_meters and (grid_origin_x is not None and p.is_latlong()): LOG.error("Lat/Lon grid '%s' must have its origin in degrees", grid_name) raise ValueError("Lat/Lon grid '%s' must have its origin in degrees" % (grid_name,)) proj4_dict = get_proj4_info(proj4_str) info.update(**proj4_dict) info["grid_kind"] = "proj4" info["static"] = static info["proj4_str"] = proj4_str info["pixel_size_x"] = pixel_size_x info["pixel_size_y"] = pixel_size_y info["grid_origin_x"] = grid_origin_x info["grid_origin_y"] = grid_origin_y info["grid_width"] = grid_width info["grid_height"] = grid_height return info
def python_ll2cr(lon_arr, lat_arr, grid_info, fill_in=numpy.nan, fill_out=None, cols_out=None, rows_out=None): """Project longitude and latitude points to column rows in the specified grid. :param lon_arr: Numpy array of longitude floats :param lat_arr: Numpy array of latitude floats :param grid_info: dictionary of grid information (see below) :param fill_in: (optional) Fill value for input longitude and latitude arrays (default: NaN) :param fill_out: (optional) Fill value for output column and row array (default: `fill_in`) :returns: tuple(points_in_grid, cols_out, rows_out) The provided grid info must have the following parameters (optional grids mean dynamic): - proj4_definition - cell_width - cell_height - width (optional/None) - height (optional/None) - origin_x (optional/None) - origin_y (optional/None) Steps taken in this function: 1. Convert (lon, lat) points to (X, Y) points in the projection space 2. If grid is missing some parameters (dynamic grid), then fill them in 3. Convert (X, Y) points to (column, row) points in the grid space """ p = Proj(grid_info["proj4_definition"]) cw = grid_info["cell_width"] ch = grid_info["cell_height"] w = grid_info.get("width", None) h = grid_info.get("height", None) ox = grid_info.get("origin_x", None) oy = grid_info.get("origin_y", None) is_static = None not in [w, h, ox, oy] proj_circum = projection_circumference(p) if rows_out is None: rows_out = numpy.empty_like(lat_arr) if cols_out is None: cols_out = numpy.empty_like(lon_arr) if fill_out is None: fill_out = fill_in mask = ~(mask_helper(lon_arr, fill_in) | mask_helper(lat_arr, fill_in)) x, y = p(lon_arr, lat_arr) mask = mask & (x < 1e30) & (y < 1e30) # need temporary storage because x and y are might NOT be copies (latlong projections) cols_out[:] = numpy.where(mask, x, fill_out) rows_out[:] = numpy.where(mask, y, fill_out) # we only need the good Xs and Ys from here on out x = cols_out[mask] y = rows_out[mask] if not is_static: # fill in grid parameters xmin = numpy.nanmin(x) xmax = numpy.nanmax(x) ymin = numpy.nanmin(y) ymax = numpy.nanmax(y) # if the data seems to be covering more than 75% of the projection space then the antimeridian is being crossed # if proj_circum is None then we can't simply wrap the data around projection, the grid will probably be large LOG.debug("Projection circumference: %f", proj_circum) if proj_circum is not None and xmax - xmin >= proj_circum * .75: old_xmin = xmin old_xmax = xmax x[x < 0] += proj_circum xmin = numpy.nanmin(x) xmax = numpy.nanmax(x) LOG.debug( "Data seems to cross the antimeridian: old_xmin=%f; old_xmax=%f; xmin=%f; xmax=%f", old_xmin, old_xmax, xmin, xmax) LOG.debug("Xmin=%f; Xmax=%f; Ymin=%f; Ymax=%f", xmin, xmax, ymin, ymax) if ox is None: # upper-left corner ox = grid_info["origin_x"] = float(xmin) oy = grid_info["origin_y"] = float(ymax) LOG.debug("Dynamic grid origin (%f, %f)", xmin, ymax) if w is None: w = grid_info["width"] = int(abs((xmax - xmin) / cw)) h = grid_info["height"] = int(abs((ymax - ymin) / ch)) LOG.debug( "Dynamic grid width and height (%d x %d) with cell width and height (%f x %f)", w, h, cw, ch) good_cols = (x - ox) / cw good_rows = (y - oy) / ch cols_out[mask] = good_cols rows_out[mask] = good_rows points_in_grid = numpy.count_nonzero((good_cols >= -1) & (good_cols <= w + 1) & (good_rows >= -1) & (good_rows <= h + 1)) return points_in_grid, cols_out, rows_out