def test_gdal_tile_untile(self):
        img = np.arange(0., 100.).reshape((10, 10))
        path = os.path.join(os.getcwd(), "test_gdal_retile.tif")
        tile_folder = os.path.join(os.getcwd(), "tiled")
        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        # Add parasitic file - It should not cause problems
        path_parasite = os.path.join(tile_folder, "tile_01_01.tif")
        FileSystem.create_directory(tile_folder)
        ImageIO.write_geotiff(img, path_parasite, self.projection, self.coordinates)
        ds_in = GDalDatasetWrapper.from_file(path)
        self.assertTrue(os.path.exists(path))
        tiles = ImageTools.gdal_retile(ds_in, tile_folder,
                                       TileWidth=2,
                                       TileHeight=2,
                                       Overlap=1)
        self.assertTrue(os.path.isdir(tile_folder))
        self.assertEqual(len(tiles), 81)
        img_read = np.array(ImageIO.tiff_to_array(tiles[-1]))
        expected = np.array([[88, 89],
                             [98, 99]])
        # Some gdal_retile versions are producing the following image:
        # [[87, 89], [97, 99]].
        np.testing.assert_allclose(expected, img_read, atol=1)

        # Untile
        ds_untiled = ImageTools.gdal_buildvrt(*tiles)
        np.testing.assert_allclose(img, ds_untiled.array, atol=1)
        FileSystem.remove_file(path)
        FileSystem.remove_directory(tile_folder)
 def test_write_read_geotiff_kwoptions(self):
     img = np.ones((self.height, self.width), np.int16)
     nodata = 42
     path = os.path.join(os.getcwd(), "test_write_read_geotiff.tif")
     ImageIO.gdal_write("GTiff",
                        img,
                        path,
                        self.projection,
                        self.coordinates,
                        options=["COMPRESS=DEFLATE"],
                        nodata=nodata)
     self.assertTrue(os.path.exists(path))
     arr, ds = ImageIO.tiff_to_array(path, array_only=False)
     self.assertTrue((arr == img).all())
     self.assertEqual(
         nodata,
         gdal.Info(ds, format="json")["bands"][0]["noDataValue"])
     self.assertEqual(
         gdal.Info(
             ds,
             format="json")["metadata"]["IMAGE_STRUCTURE"]["COMPRESSION"],
         "DEFLATE")
     self.assertEqual(ds.GetGeoTransform(), self.coordinates)
     # Compare projections by removing all spaces cause of multiline string
     self.assertEqual(ds.GetProjection().replace(" ", ""),
                      self.projection.replace(" ", ""))
     FileSystem.remove_file(path)
     self.assertFalse(os.path.exists(path))
 def test_get_s2_epsg_code(self):
     epsg_ref = 32631
     projection = 'PROJCS["WGS 84 / UTM zone 31N",\
                     GEOGCS["WGS 84",DATUM["WGS_1984",\
                     SPHEROID["WGS 84",6378137,298.257223563,\
                     AUTHORITY["EPSG","7030"]],\
                     AUTHORITY["EPSG","6326"]],\
                     PRIMEM["Greenwich",0,\
                     AUTHORITY["EPSG","8901"]],\
                     UNIT["degree",0.0174532925199433,\
                     AUTHORITY["EPSG","9122"]],\
                     AUTHORITY["EPSG","4326"]],\
                     PROJECTION["Transverse_Mercator"],\
                     PARAMETER["latitude_of_origin",0],\
                     PARAMETER["central_meridian",3],\
                     PARAMETER["scale_factor",0.9996],\
                     PARAMETER["false_easting",500000],\
                     PARAMETER["false_northing",0],\
                     UNIT["metre",1,AUTHORITY["EPSG","9001"]],\
                     AXIS["Easting",EAST],AXIS["Northing",NORTH],\
                     AUTHORITY["EPSG","%s"]]' % epsg_ref
     img = np.ones((self.height, self.width), np.int16)
     path = os.path.join(os.getcwd(), "test_epsg.tif")
     ImageIO.write_geotiff(img, path, projection, self.coordinates)
     self.assertTrue(os.path.exists(path))
     ds = GDalDatasetWrapper.from_file(path)
     epsg_new = ds.epsg
     self.assertEqual(epsg_new, epsg_ref)
     FileSystem.remove_file(path)
     self.assertFalse(os.path.exists(path))
    def test_override_array(self):
        img = np.ones((self.height, self.width), np.int16)
        img_new = np.array(np.arange(0, 40200).reshape(201, 200),
                           dtype=np.uint8)
        path = os.path.join(os.getcwd(), "test_write_read_geotiff.tif")
        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        self.assertTrue(os.path.exists(path))

        ds = GDalDatasetWrapper.from_file(path)
        self.assertTrue((ds.array == img).all())
        self.assertEqual(ds.geotransform, self.coordinates)
        self.assertEqual(ds.projection.replace(" ", ""),
                         self.projection.replace(" ", ""))
        self.assertIsNone(ds.nodata_value)
        self.assertEqual(ds.epsg, 32631)
        ds_new = GDalDatasetWrapper(ds=ds.get_ds(),
                                    array=img_new,
                                    nodata_value=123)
        FileSystem.remove_file(path)
        np.testing.assert_almost_equal(img_new, ds_new.array)
        self.assertEqual(ds_new.geotransform, self.coordinates)
        self.assertEqual(ds_new.projection.replace(" ", ""),
                         self.projection.replace(" ", ""))
        self.assertEqual(ds_new.nodata_value, 123)
        self.assertEqual(ds_new.epsg, 32631)
        self.assertFalse(os.path.exists(path))
    def test_gdal_buildvrt_concatenate(self):
        from StartMaja.Common import FileSystem
        paths = []
        for i in range(1, 3, 1):
            img = np.ones((i, i, 2), np.int16) * i
            path = os.path.join(os.getcwd(), "test_gdal_merge_%s.tif" % i)
            ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
            self.assertTrue(os.path.exists(path))
            paths.append(path)
        empty = os.path.join(os.getcwd(), "empty.vrt")
        driver = ImageTools.gdal_buildvrt(*paths, dst=empty,
                                          separate=True,
                                          srcnodata=0)
        expected = np.array([[[1, 0],
                              [0, 0]],
                             [[2, 2],
                              [2, 2]]], dtype=np.int16)

        np.testing.assert_almost_equal(driver.array, expected)
        self.assertEqual(driver.nodata_value, 0)
        self.assertEqual(driver.epsg, 32631)
        [FileSystem.remove_file(path) for path in paths]
        FileSystem.remove_file(empty)
        [self.assertFalse(os.path.exists(path)) for path in paths]
        self.assertFalse(os.path.exists(empty))
 def lonlat_minmax(self):
     """
     Get lat and lon min and max values
     :return: latmin, latmax, lonmin, lonmax of the current sites
     """
     ul_lonlat = ImageIO.transform_point(self.ul, self.epsg, new_epsg=4326)
     lr_lonlat = ImageIO.transform_point(self.lr, self.epsg, new_epsg=4326)
     return ul_lonlat, lr_lonlat
 def test_get_nodata(self):
     expected_nodata = 42.0
     img = np.ones((self.height, self.width), np.int16)
     path = os.path.join(os.getcwd(), "test_get_nodata_init.tif")
     ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
     ds = ImageTools.gdal_buildvrt(path, VRTNodata=expected_nodata)
     self.assertEqual(expected_nodata, ds.nodata_value)
     np.testing.assert_almost_equal(ds.nodata_mask, np.ones_like(img))
     FileSystem.remove_file(path)
     self.assertFalse(os.path.exists(path))
 def test_get_utm_description(self):
     img = np.ones((self.height, self.width), np.int16)
     path = os.path.join(os.getcwd(), "test_get_utm_description.tif")
     ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
     self.assertTrue(os.path.exists(path))
     ds = GDalDatasetWrapper.from_file(path)
     utm = ds.utm_description
     utm_expected = "WGS 84 / UTM zone 31N"
     self.assertEqual(utm_expected, utm)
     FileSystem.remove_file(path)
     self.assertFalse(os.path.exists(path))
    def test_gdal_buildvrt(self):
        path = os.path.join(os.getcwd(), "test_gdal_buildvrt.tif")
        vrt = os.path.join(os.getcwd(), "test_vrt.vrt")
        img = np.arange(-4, 5).reshape(3, 3) / 5

        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        self.assertTrue(os.path.exists(path))
        driver = ImageTools.gdal_buildvrt(path, dst=vrt)
        self.assertTrue(os.path.exists(vrt))
        np.testing.assert_almost_equal(driver.array, img)
        FileSystem.remove_file(vrt)
        FileSystem.remove_file(path)
    def test_get_resolution(self):
        img = np.ones((self.height, self.width), np.int16)
        path = os.path.join(os.getcwd(), "test_get_resolution.tif")
        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        self.assertTrue(os.path.exists(path))

        ds = GDalDatasetWrapper.from_file(path)
        res_expected = (self.coordinates[1], self.coordinates[-1])
        self.assertEqual(res_expected, ds.resolution)

        FileSystem.remove_file(path)
        self.assertFalse(os.path.exists(path))
    def test_get_ul_lr(self):
        img = np.ones((1000, 1000), np.int16)
        path = os.path.join(os.getcwd(), "test_get_ul_lr.tif")
        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        self.assertTrue(os.path.exists(path))

        ds = GDalDatasetWrapper.from_file(path)
        ulx, uly, lrx, lry = ds.ul_lr
        self.assertEqual((ulx, uly), (300000.0, 4900020.0))
        self.assertEqual((lrx, lry), (310000.0, 4890020.0))
        self.assertEqual(ds.extent, (300000.0, 4890020.0, 310000.0, 4900020.0))
        FileSystem.remove_file(path)
        self.assertFalse(os.path.exists(path))
    def test_write_read_geotiff(self):
        img = np.ones((self.height, self.width), np.int16)
        path = os.path.join(os.getcwd(), "test_write_read_geotiff.tif")
        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        self.assertTrue(os.path.exists(path))

        arr, ds = ImageIO.tiff_to_array(path, array_only=False)
        self.assertTrue((arr == img).all())
        self.assertEqual(ds.GetGeoTransform(), self.coordinates)
        # Compare projections by removing all spaces cause of multiline string
        self.assertEqual(ds.GetProjection().replace(" ", ""),
                         self.projection.replace(" ", ""))
        FileSystem.remove_file(path)
        self.assertFalse(os.path.exists(path))
 def test_faulty_geotransform_projection(self):
     coordinates = (652594.9112913811, 10.00887639510383, 0,
                    5072876.717295351, 0)  # Missing one value
     projection = ''  # Empty
     height = 200
     width = 100
     img = np.ones((height, width), np.int16)
     path = os.path.join(os.getcwd(),
                         "test_faulty_geotransform_projection.tif")
     # Check geotransform wrong:
     with self.assertRaises(TypeError):
         ImageIO.write_geotiff(img, path, projection, coordinates)
     FileSystem.remove_file(path)
     self.assertFalse(os.path.exists(path))
 def test_gdal_translate(self):
     img = np.ones((self.height, self.width, 2), np.int16)
     path = os.path.join(os.getcwd(), "test_gdal_translate.tif")
     scaled = os.path.join(os.getcwd(), "scaled.tif")
     out_resolution = (20, -20)
     ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
     self.assertTrue(os.path.exists(path))
     ds = ImageTools.gdal_translate(path, scaled,
                                    tr=" ".join([str(i) for i in out_resolution]),
                                    scale="0 1 0 255")
     self.assertEqual(ds.resolution, out_resolution)
     self.assertEqual(ds.array.shape, (self.height // 2, self.width // 2, 2))
     np.testing.assert_almost_equal(ds.array, 255)
     FileSystem.remove_file(path)
     FileSystem.remove_file(scaled)
 def get_synthetic_band(self, synthetic_band, **kwargs):
     wdir = kwargs.get("wdir", self.fpath)
     output_folder = os.path.join(wdir, self.base)
     output_bname = "_".join(
         [self.base.split(".")[0],
          synthetic_band.upper() + ".tif"])
     output_filename = kwargs.get("output_filename",
                                  os.path.join(output_folder, output_bname))
     max_value = kwargs.get("max_value", 10000.)
     # Skip existing:
     if os.path.exists(output_filename):
         return output_filename
     if synthetic_band.lower() == "ndvi":
         FileSystem.create_directory(output_folder)
         b4 = self.find_file(pattern=r"*B0?4(_10m)?.jp2$")[0]
         b8 = self.find_file(pattern=r"*B0?8(_10m)?.jp2$")[0]
         ds_red = GDalDatasetWrapper.from_file(b4)
         ds_nir = GDalDatasetWrapper.from_file(b8)
         ds_ndvi = ImageApps.get_ndvi(ds_red,
                                      ds_nir,
                                      vrange=(0, max_value),
                                      dtype=np.int16)
         ds_ndvi.write(output_filename, options=["COMPRESS=DEFLATE"])
     elif synthetic_band.lower() == "ndsi":
         FileSystem.create_directory(output_folder)
         b3 = self.find_file(pattern=r"*B0?3(_10m)?.jp2$")[0]
         b11 = self.find_file(pattern=r"*B11(_20m)?.jp2$")[0]
         ds_green = ImageTools.gdal_translate(b3, tr="20 20", r="cubic")
         ds_swir = GDalDatasetWrapper.from_file(b11)
         ds_ndsi = ImageApps.get_ndsi(ds_green,
                                      ds_swir,
                                      vrange=(0, max_value),
                                      dtype=np.int16)
         ds_ndsi.write(output_filename, options=["COMPRESS=DEFLATE"])
     elif synthetic_band.lower() == "mca_sim":
         FileSystem.create_directory(output_folder)
         b4 = self.find_file(pattern=r"*B0?4(_10m)?.jp2$")[0]
         b3 = self.find_file(pattern=r"*B0?3(_10m)?.jp2$")[0]
         img_red, drv = ImageIO.tiff_to_array(b4, array_only=False)
         img_green = ImageIO.tiff_to_array(b3)
         img_mcasim = (img_red + img_green) / 2
         ImageIO.write_geotiff_existing(img_mcasim,
                                        output_filename,
                                        drv,
                                        options=["COMPRESS=DEFLATE"])
     else:
         raise ValueError("Unknown synthetic band %s" % synthetic_band)
     return output_filename
 def test_transform_point_lat_lon(self):
     center = (653095.355, 5071879.228)
     lon_lat_expected = (4.9694934619557145, 45.78349724618419)
     lon_lat_calc = ImageIO.transform_point(center,
                                            old_epsg=32631,
                                            new_epsg=4326)
     np.testing.assert_almost_equal(lon_lat_calc, lon_lat_expected)
    def test_write_read_geotiff(self):
        img = np.ones((self.height, self.width), np.int16)
        path = os.path.join(os.getcwd(), "test_write_read_geotiff.tif")
        ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
        self.assertTrue(os.path.exists(path))

        ds = GDalDatasetWrapper.from_file(path)
        self.assertTrue((ds.array == img).all())
        self.assertEqual(ds.geotransform, self.coordinates)
        # Compare projections by removing all spaces cause of multiline string
        self.assertEqual(ds.projection.replace(" ", ""),
                         self.projection.replace(" ", ""))
        self.assertIsNone(ds.nodata_value)
        self.assertEqual(ds.epsg, 32631)
        FileSystem.remove_file(path)
        self.assertFalse(os.path.exists(path))
 def test_gdal_warp(self):
     img = np.ones((self.height, self.width, 2), np.int16)
     img_rescaled = np.ones((int(self.height/2), int(self.width/2), 2), np.int16)
     out_resolution = (20, -20)
     path = os.path.join(os.getcwd(), "test_gdal_warp.tif")
     scaled = os.path.join(os.getcwd(), "res_changed.tif")
     ImageIO.write_geotiff(img, path, self.projection, self.coordinates)
     self.assertTrue(os.path.exists(path))
     ds = ImageTools.gdal_warp(path, scaled,
                               tr=" ".join(str(e) for e in out_resolution),
                               r="max", q=True)
     self.assertTrue(os.path.isfile(scaled))
     self.assertEqual(ds.resolution, out_resolution)
     self.assertEqual(ds.array.shape, (self.height // 2, self.width // 2, 2))
     np.testing.assert_almost_equal(ds.array, img_rescaled)
     FileSystem.remove_file(path)
     FileSystem.remove_file(scaled)
    def test_merge_then_translate(self):
        datasets = []
        init = np.zeros((2, 2), np.int16)
        path = os.path.join(os.getcwd(), "test_gdal_merge.tif")
        ImageIO.write_geotiff(init, path, self.projection, self.coordinates)
        ds_in = GDalDatasetWrapper.from_file(path)
        for i in range(1, 3, 1):
            img = np.ones((i*2, i*2), np.int16) * i
            ds_n = GDalDatasetWrapper(ds=ds_in.get_ds(), array=img)
            self.assertTrue(os.path.exists(path))
            datasets.append(ds_n)
        ds_merged = ImageTools.gdal_merge(*datasets, dst="out.tif",
                                          separate=True,
                                          q=True,
                                          a_nodata=0)
        # Array of shape (4, 4, 2):
        expected = np.array([[[1, 2],
                              [1, 2],
                              [0, 2],
                              [0, 2]],
                             [[1, 2],
                              [1, 2],
                              [0, 2],
                              [0, 2]],
                             [[0, 2],
                              [0, 2],
                              [0, 2],
                              [0, 2]],
                             [[0, 2],
                              [0, 2],
                              [0, 2],
                              [0, 2]]], dtype=np.int16)
        FileSystem.remove_file("out.tif")
        np.testing.assert_equal(expected.dtype, ds_merged.array.dtype)
        np.testing.assert_almost_equal(expected, ds_merged.array)
        self.assertEqual(ds_merged.nodata_value, 0)
        self.assertEqual(ds_merged.epsg, 32631)
        ds_translate = ImageTools.gdal_translate(ds_merged,
                                                 a_nodata=0)
        FileSystem.remove_file(path)

        np.testing.assert_equal(expected.dtype, ds_translate.array.dtype)
        np.testing.assert_almost_equal(ds_translate.array, expected)
        self.assertEqual(ds_translate.nodata_value, 0)
        self.assertEqual(ds_translate.epsg, 32631)
    def from_file(cls, p_in):
        """
        Create a :class:`GDalDatasetWrapper` from a file

        :param p_in: The path to the geo-referenced file
        :return: A GDalDatasetWrapper object
        """
        ds = ImageIO.open_tiff(p_in)
        return cls(ds=ds, p_in=p_in)
    def test_gdal_merge_optfile(self):
        datasets, written = [], []
        init = np.zeros((2, 2), np.int16)
        path = os.path.join(os.getcwd(), "test_gdal_merge_optfile.tif")
        ImageIO.write_geotiff(init, path, self.projection, self.coordinates)
        ds_in = GDalDatasetWrapper.from_file(path)
        for i in range(1, 3, 1):
            img = np.ones((i*2, i*2), np.int16) * i
            ds_n = GDalDatasetWrapper(ds=ds_in.get_ds(), array=img)
            p_out = "test_gdal_merge_optfile_%s.tif" % i
            ImageIO.write_geotiff_existing(img, p_out, ds_in.get_ds())
            self.assertTrue(os.path.exists(path))
            datasets.append(ds_n)
            written.append(p_out)
        optfile = "test_gdal_merge_optfile.txt"
        with open(optfile, 'w') as file_handler:
            for item in written:
                file_handler.write("{}\n".format(item))

        ds_merged = ImageTools.gdal_merge(*datasets,
                                          q=True,
                                          a_nodata=0)
        ds_optfile = ImageTools.gdal_merge(optfile=optfile,
                                           q=True,
                                           a_nodata=0)
        # Array of shape (4, 4):
        expected = np.array([[2, 2, 2, 2],
                             [2, 2, 2, 2],
                             [2, 2, 2, 2],
                             [2, 2, 2, 2]], dtype=np.int16)

        FileSystem.remove_file(path)
        [FileSystem.remove_file(p) for p in written]
        FileSystem.remove_file(optfile)
        np.testing.assert_equal(expected.dtype, ds_merged.array.dtype)
        np.testing.assert_almost_equal(expected, ds_merged.array)
        self.assertEqual(ds_merged.nodata_value, 0)
        self.assertEqual(ds_merged.epsg, 32631)
        np.testing.assert_equal(expected.dtype, ds_optfile.array.dtype)
        np.testing.assert_almost_equal(expected, ds_optfile.array)
        self.assertEqual(ds_optfile.nodata_value, 0)
        self.assertEqual(ds_optfile.epsg, 32631)
Exemple #22
0
    def get_eudem_codes(site):
        """
        Get the list of EuDEM files for a given site.
        :return: The list of filenames needed in order to cover to whole site.
        """
        grid_step = 10
        from StartMaja.Common import ImageIO
        ur_etrs89 = ImageIO.transform_point(
            (site.lr_lonlat[0], site.ul_lonlat[1]),
            old_epsg=4326,
            new_epsg=3035)
        ll_etrs89 = ImageIO.transform_point(
            (site.ul_lonlat[0], site.lr_lonlat[1]),
            old_epsg=4326,
            new_epsg=3035)

        def myfloor(val, base=grid_step):
            return base * math.floor(val / base)

        lon_min = myfloor(ll_etrs89[0] / 100000)
        lon_max = myfloor(ur_etrs89[0] / 100000)
        lat_min = myfloor(ll_etrs89[1] / 100000)
        lat_max = myfloor(ur_etrs89[1] / 100000)
        lat_codes = list(range(lat_min, lat_max + grid_step, grid_step))
        lon_codes = list(range(lon_min, lon_max + grid_step, grid_step))
        eudem_granules = []
        for x in lon_codes:
            for y in lat_codes:
                code_lat = "S" if y < 0 else "N"
                code_lon = "W" if x < 0 else "E"
                eudem_code = "eu_dem_v11_%s%s%s%s" % (code_lon, x, code_lat, y)
                if eudem_code not in eudem_archives:
                    raise ValueError(
                        "Cannot process this product with EuDEM."
                        "(Partially) out of region:"
                        "%s -> [%s, %s]" %
                        (eudem_code, site.lr_lonlat, site.ul_lonlat))
                eudem_granules.append(eudem_code)
        return eudem_granules
    def test_write_read_memory(self):
        img = np.ones((self.height, self.width), np.int16)
        path = "/vsimem/test_write_read_memory.tif"
        ds = ImageIO.write_to_memory(img, path, self.projection,
                                     self.coordinates)

        arr = ds.ReadAsArray()
        self.assertTrue((arr == img).all())
        self.assertEqual(ds.GetGeoTransform(), self.coordinates)
        # Compare projections by removing all spaces cause of multiline string
        self.assertEqual(ds.GetProjection().replace(" ", ""),
                         self.projection.replace(" ", ""))
        ds = None  # Always remember to dereference :)
        FileSystem.remove_file(path)
    def get_ds(self):
        """
        Return a new copy of a :class:`gdal.Dataset`.

        :return: A gdal dataset object
        """
        p_out = "/vsimem/" + uuid.uuid4().hex
        try:
            nodata = self.nodata_value
        except AttributeError:
            nodata = None
        return ImageIO.write_to_memory(self.array,
                                       p_out,
                                       self.projection,
                                       self.geotransform,
                                       nodata=nodata)
    def write(self, p_out, **kwargs):
        """
        Write the array to a given location

        :param p_out: Location to write to, overrides internal `self._p_out` parameter
        :keyword kwargs: Optional gdal keyword arguments.
        :return: Writes array to given location
        """
        nodata = kwargs.pop("nodata", self.nodata_value)
        driver = kwargs.pop("driver", "GTiff")
        return ImageIO.gdal_write(driver,
                                  self.array,
                                  p_out,
                                  self.projection,
                                  self.geotransform,
                                  nodata=nodata,
                                  **kwargs)
    def s2_epsg_code(self):
        """
        Get the Sentinel-2 EPSG Code for the current dataset
        The codes range from 32601..60 and 32701..60

        :return: The EPSG-Code as int. E.g. '32630'
        :rtype: int
        """
        ul, lr = self.ul_lr
        epsg_old = self.epsg
        if epsg_old != 4326:
            lon, lat = ImageIO.transform_point(ul, epsg_old)
        else:
            lat, lon = ul
        lon_mod = int(lon / 6)

        lon_code = str(30 + lon_mod if lon < 0 else 31 - lon_mod).zfill(2)
        epsg = "327" if lat < 0 else "326"
        return int(epsg + lon_code)