基于GDAL的影像切圖工具

1 功能需求

????對一副3857坐標系的衛星影像按照谷歌TMS進行切片,并將切片文件存儲在符合MBTiles規范的SQLite數據庫中

2 依賴庫

GDAL ,GeoTools

3 環境搭建

????由于個人對Java語言比較熟悉,因此使用Java語言開發環境,Java工程復用原有的SpringBoot工程,然后在pom文件中添加對GeoTools庫的依賴即可實現GeoTools的引入。

3.1 Windows環境下搭建GDAL Java Binding開發環境

????GDAL是當前最流行的遙感影像處理庫,提供了一系列對遙感影像數據的操作函數,支持常見的影像數據格式,并提供了相應的抽象結構方便進行算法操作。GDAL的源代碼是用C和C++編寫的,并通過SWIG發布其他語言版本的bindings。
????首先訪問http://www.gisinternals.com/下載對應版本的編譯好的GDAL庫,下載并解壓到本地文件夾下,注意路徑不能有中文。然后在計算機-環境變量Path中添加對GDAL DLL的引用。E:\soft\GIS\gdal\bin;E:\soft\GIS\gdal\bin\gdal\java
????在E:\soft\GIS\gdal\bin\gdal\java即可找到GDAL對應的JAR包,在工程中引用后即完成了GDAL的引入。完成后運行測試代碼,成功顯示影像信息后即代表GDAL庫引入成功。

public class GDALTest {
    public static void main(String[] args) {
        gdal.AllRegister();
        String fileName = "D:\\images\\2011_output_cai.tif";
        Dataset dataset = gdal.Open(fileName, gdalconstConstants.GA_ReadOnly);
        if (dataset == null) {
            System.err.println("GDALOpen failed - " + gdal.GetLastErrorNo());
            System.err.println(gdal.GetLastErrorMsg());

            System.exit(1);
        }
        double[] ori_transform = dataset.GetGeoTransform();
        System.out.println(String.format("Origin = (%s, %s)", ori_transform[0], ori_transform[3]));
        System.out.println(String.format("Pixel Size = (%s, %s)", ori_transform[1], ori_transform[5]));
    }
}
4 切片處理流程
4.1 首先獲取原始影像的地理坐標范圍
double[] ori_transform = dataset.GetGeoTransform();
double latMin = ori_transform[3];
double latMax = ori_transform[3] + (yCount * ori_transform[1]);
double lonMin = ori_transform[0];
double lonMax = ori_transform[0] + (xCount * ori_transform[1]);
ReferencedEnvelope imageBound = new ReferencedEnvelope(lonMin, lonMax, latMin, latMax, crs);
4.2 獲取原始影像的像素分辨率
int xCount = dataset.getRasterXSize();
int yCount = dataset.getRasterYSize();
// 原始圖像東西方向像素分辨率
double src_w_e_pixel_resolution = (lonMax - lonMin) / xCount;
// 原始圖像南北方向像素分辨率
double src_n_s_pixel_resolution = (latMax - latMin) / yCount;
4.3 根據原始影像地理范圍求解切片行列號
int tileRowMax = Utils.getOSMTileYFromLatitude(latMin, zoom);
int tileRowMin = Utils.getOSMTileYFromLatitude(latMax, zoom);
int tileColMin = Utils.getOSMTileXFromLongitude(lonMin, zoom);
int tileColMax = Utils.getOSMTileXFromLongitude(lonMax, zoom);
4.4 求原始影像地理范圍與指定縮放級別指定行列號的切片交集
double tempLatMin = tile2lat(row + 1, zoom);
double tempLatMax = tile2lat(row, zoom);
double tempLonMin = tile2lon(col, zoom);
double tempLonMax = tile2lon(col + 1, zoom);
ReferencedEnvelope tileBound = new ReferencedEnvelope(tempLonMin, tempLonMax, tempLatMin,tempLatMax, crs);
ReferencedEnvelope intersect = tileBound.intersection(imageBound);
4.5 求解當前切片的像素分辨率(默認切片大小為256*256)
// 切片東西方向像素分辨率
double dst_w_e_pixel_resolution = (tempLonMax - tempLonMin) / 256;
// 切片南北方向像素分辨率
double dst_n_s_pixel_resolution = (tempLatMax - tempLatMin) / 256;
4.6 計算交集的像素信息
// 求切圖范圍和原始圖像交集的起始點像素坐標
int offset_x = (int) ((intersect.getMinX() - lonMin) / src_w_e_pixel_resolution);
int offset_y = (int) Math.abs((intersect.getMaxY() - latMax) / src_n_s_pixel_resolution);

// 求在切圖地理范圍內的原始圖像的像素大小
int block_xsize = (int) ((intersect.getMaxX() - intersect.getMinX()) / src_w_e_pixel_resolution);
int block_ysize = (int) ((intersect.getMaxY() - intersect.getMinY()) / src_n_s_pixel_resolution);

// 求原始圖像在切片內的像素大小
int image_Xbuf = (int) Math.ceil((intersect.getMaxX() - intersect.getMinX()) / dst_w_e_pixel_resolution);
int image_Ybuf = (int) Math.ceil(Math.abs((intersect.getMaxY() - intersect.getMinY()) / dst_n_s_pixel_resolution));

// 求原始圖像在切片中的偏移坐標
int imageOffsetX = (int) ((intersect.getMinX() - tempLonMin) / dst_w_e_pixel_resolution);
int imageOffsetY = (int) Math.abs((intersect.getMaxY() - tempLatMax) / dst_n_s_pixel_resolution);
imageOffsetX = imageOffsetX > 0 ? imageOffsetX : 0;
imageOffsetY = imageOffsetY > 0 ? imageOffsetY : 0;
4.7 使用GDAL的ReadRaster方法對影像指定范圍進行讀取與壓縮。
推薦在切片前建立原始影像的金字塔文件,ReadRaster在內部實現中可直接讀取相應級別的金字塔文件,提高效率。
Band in_band1 = dataset.GetRasterBand(1);
Band in_band2 = dataset.GetRasterBand(2);
Band in_band3 = dataset.GetRasterBand(3);

int[] band1BuffData = new int[256 * 256 * gdalconst.GDT_Int32];
int[] band2BuffData = new int[256 * 256 * gdalconst.GDT_Int32];
int[] band3BuffData = new int[256 * 256 * gdalconst.GDT_Int32];

in_band1.ReadRaster(offset_x, offset_y, block_xsize, block_ysize, image_Xbuf, image_Ybuf,gdalconst.GDT_Int32, band1BuffData, 0, 0);
in_band2.ReadRaster(offset_x, offset_y, block_xsize, block_ysize, image_Xbuf, image_Ybuf,gdalconst.GDT_Int32, band2BuffData, 0, 0);
in_band3.ReadRaster(offset_x, offset_y, block_xsize, block_ysize, image_Xbuf, image_Ybuf,gdalconst.GDT_Int32, band3BuffData, 0, 0);
4.8 將切片數據寫入文件
// 使用gdal的MEM驅動在內存中創建一塊區域存儲圖像數組
Driver memDriver = gdal.GetDriverByName("MEM");
Dataset msmDS = memDriver.Create("msmDS", 256, 256, 4);
Band dstBand1 = msmDS.GetRasterBand(1);
Band dstBand2 = msmDS.GetRasterBand(2);
Band dstBand3 = msmDS.GetRasterBand(3);

// 設置alpha波段數據,實現背景透明
Band alphaBand = msmDS.GetRasterBand(4);
int[] alphaData = new int[256 * 256 * gdalconst.GDT_Int32];
for (int index = 0; index < alphaData.length; index++) {
    if (band1BuffData[index] > 0) {
        alphaData[index] = 255;
    }
}
// 寫各個波段數據
dstBand1.WriteRaster(imageOffsetX, imageOffsetY, image_Xbuf, image_Ybuf, band1BuffData);
dstBand2.WriteRaster(imageOffsetX, imageOffsetY, image_Xbuf, image_Ybuf, band2BuffData);
dstBand3.WriteRaster(imageOffsetX, imageOffsetY, image_Xbuf, image_Ybuf, band3BuffData);
alphaBand.WriteRaster(imageOffsetX, imageOffsetY, image_Xbuf, image_Ybuf, alphaData);

String tileFolder = System.getProperty("java.io.tmpdir");
String pngPath = tileFolder + File.separator + zoom + "c" + col + "r" + row +".png";
// 使用PNG驅動將內存中的圖像數組寫入文件
Driver pngDriver = gdal.GetDriverByName("PNG");
Dataset pngDs = pngDriver.CreateCopy(pngPath, msmDS);

// 釋放內存
msmDS.FlushCache();
pngDs.delete();
4.8 讀取臨時文件,并轉換成二進制存儲到SQLite
try {
    InputStream fis = new FileInputStream(tileFile);
    byte[] imgBytes = toByteArray(fis);

    String uuid = UUID.randomUUID().toString();
    PreparedStatement mapPs = conn.prepareStatement("insert into map(zoom_level,tile_column,tile_row,tile_id) values (?,?,?,?)");
    PreparedStatement imagesPs = conn.prepareStatement("insert into images(tile_data,tile_id) values (?,?)");

         imagesPs.setBytes(1, imgBytes);
         imagesPs.setString(2, uuid);

         mapPs.setInt(1, zoom);
         mapPs.setInt(2, col);
         mapPs.setInt(3, row);
         mapPs.setString(4, uuid);

         imagesPs.executeUpdate();
         mapPs.executeUpdate();

         imagesPs.close();
         mapPs.close();
         fis.close();
         tileFile.delete();
} catch (Exception e) {
    e.printStackTrace();
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380