地图下载器工具-Java

地图下载器工具-Java

瓦片下载
  • 要点1
  • 使用瓦片的访问地址直接下载瓦片
    主要用到的是HttpURLConnection去访问,为了避免被封杀ip,设置了两个请求参数referer,User-Agent,目前还未被封杀。
@Override
	public void run() {
		// 定义在try外面,用于在finally关闭资源链接
		HttpURLConnection urlConnection = null;
		InputStream inputStream = null;
		OutputStream outputStream = null;
		URL url = null;
		try {
			File file = new File(downloadDir + z + File.separator + x + File.separator + y + ".jpg");
			url = new URL(String.format(link, (int) (Math.random() * 4), x, y, z));
			// 如果本地没有图片
			if (!file.exists()) {
				urlConnection = (HttpURLConnection) url.openConnection();
				urlConnection.setConnectTimeout(2000);
				urlConnection.setRequestMethod("GET");
				urlConnection.setRequestProperty("referer", "域名来源地址");
				urlConnection.addRequestProperty("User-Agent",
						"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0");
				urlConnection.connect();
				if (urlConnection.getResponseCode() == 200) {
					if (!file.getParentFile().exists()) {
						file.getParentFile().mkdirs();
					}
					file.createNewFile();
					inputStream = urlConnection.getInputStream();
					outputStream = new FileOutputStream(file);
					byte[] bytes = new byte[1024];
					int len = 0;
					while ((len = inputStream.read(bytes)) != -1) {
						outputStream.write(bytes, 0, len);
					}
					outputStream.close();
					inputStream.close();
					synchronized (failCount) {
						successCount++;
					}
				} else {
					synchronized (successCount) {
						logger.info("下载失败瓦片链接" + url);
						failCount++;
					}
				}
			} else {
				synchronized (successCount) {
					existCount++;
				}
			}
		} catch (Exception e) {
			synchronized (successCount) {
				logger.info("下载失败瓦片链接" + url);
				failCount++;
			}
		}
	}
  • 要点2
  • 根据经纬度与级别计算瓦片的编号。经过多次测试与计算,发现当最大经度为180时,会计算出多余并且不存在的瓦片编号,所以设置为179.99999;当纬度要是为-90或90的时候,由于极地的极大化,也可以认为是投影计算的规则,这里使用tan计算,所以会照成计算数据的不准确,按照正常的经纬度坐标系也是-85~85,所以这里做了限制。
    /**
	 * 根据经纬度计算行列号
	 * 
	 * @param lonlat
	 * @param level
	 * @return
	 */
	public static TileRowColAndCount getTileColRow(String[] lonlat, Integer level) {

		double maxlon = Math.max(Double.valueOf(lonlat[0]), Double.valueOf(lonlat[2])) == 180.0 ? 179.99999
				: Math.max(Double.valueOf(lonlat[0]), Double.valueOf(lonlat[2]));
		double minlon = Math.min(Double.valueOf(lonlat[0]), Double.valueOf(lonlat[2]));
		double maxlat = Math.max(Double.valueOf(lonlat[1]), Double.valueOf(lonlat[3])) == 90.0 ? 85.0
				: Math.max(Double.valueOf(lonlat[1]), Double.valueOf(lonlat[3]));
		double minlat = Math.min(Double.valueOf(lonlat[1]), Double.valueOf(lonlat[3])) == -90.0 ? -85.0
				: Math.min(Double.valueOf(lonlat[1]), Double.valueOf(lonlat[3]));

		long startCol = Math.round(Math.floor((minlon + 180.0) / 360.0 * (1 << level)));
		long endCol = Math.round(Math.floor((maxlon + 180.0) / 360.0 * (1 << level)));
		long startRow = Math.round(Math.floor((1.0
				- Math.log(Math.tan(maxlat * Math.PI / 180.0) + 1.0 / Math.cos(maxlat * Math.PI / 180.0)) / Math.PI)
				/ 2.0 * (1 << level)));
		long endRow = Math.round(Math.floor((1.0
				- Math.log(Math.tan(minlat * Math.PI / 180.0) + 1.0 / Math.cos(minlat * Math.PI / 180.0)) / Math.PI)
				/ 2.0 * (1 << level)));
		return new TileRowColAndCount(startRow, endRow, startCol, endCol);
	}
  • 根据计算的瓦片编号循环拼接成瓦片下载地址进行下载
/**
	 * 执行下载
	 * 
	 * @param range       范围
	 * @param link        下载链接
	 * @param downloadDir 下载到本地的目录
	 * @param level       级别
	 */
	private void startDownload(TileRowColAndCount tileColRow, String link, String downloadDir, Integer level) {
		// 创建线程池
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 100, 500, TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>());
		// 遍历列行下载
		for (long col = tileColRow.startCol; col <= tileColRow.endCol; col++) {
			for (long row = tileColRow.startRow; row <= tileColRow.endRow; row++) {
				// 线程池执行下载
				threadPoolExecutor.execute(new DownloadMapTileThread(link, downloadDir, col, row, level));
			}
		}
		threadPoolExecutor.shutdown(); // 关闭线程池
		while (!threadPoolExecutor.isTerminated()) {
		} // 所有任务被执行完毕时继续往下执行
	}
瓦片拼接
  • 原理很简单:使用BufferedImage创建一张新的图片,根据配置的下载信息,拿出对应瓦片编号的图片,添加到新图片的对应位置。
    图片范围大小的限制,因为当图片太大,超过了的Integer范围,就会抛出异常。
    /**
	 * 拼接
	 * 
	 * @param tileColRow
	 * @param level
	 * @return
	 */
	private BufferedImage executeMergeImage(TileRowColAndCount tileColRow, int level) {
		// 生成新图片
		BufferedImage mergeImage = null;
		try {
			// 图片范围大小
			if ((int) ((tileColRow.endCol - tileColRow.startCol + 1) * 256) < 46340
					&& (int) ((tileColRow.endRow - tileColRow.startRow + 1) * 256) < 46340) {
				mergeImage = new BufferedImage((int) ((tileColRow.endCol - tileColRow.startCol + 1) * 256),
						(int) ((tileColRow.endRow - tileColRow.startRow + 1) * 256), BufferedImage.TYPE_INT_RGB);
				// 合并所有子图片到新图片
				int wx = 0;
				int w1 = 0;
				int h1 = 0;
				for (long col = tileColRow.startCol; col <= tileColRow.endCol; col++) {
					int wy = 0;
					for (long row = tileColRow.startRow; row <= tileColRow.endRow; row++) {
						File file = new File(
								downloadDir + level + File.separator + col + File.separator + row + ".jpg");
						if (file.exists() && (file.length() > 0)) {
							BufferedImage img = ImageIO.read(file);
							w1 = img.getWidth();
							h1 = img.getHeight();
							// 从图片中读取RGB
							int[] ImageArrayOne = new int[w1 * h1];
							ImageArrayOne = img.getRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 逐行扫描图像中各个像素的RGB到数组中
							mergeImage.setRGB(wx, wy, w1, h1, ImageArrayOne, 0, w1); // 设置上半部分或左半部分的RGB
						} else {
							continue;
						}
						wy += 256;
					}
					wx += 256;
				}
			} else {
				logger.info("拼接后的图片超出范围,请小区域拼接再导入数据库融合!");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return mergeImage;
	}
使用gdal给拼接后的图片添加坐标信息
  • gdal的使用,这是一个第三方库,使用的时候非常的坑,相关资料又少,只有多次尝试总结,希望这篇文章能对使用者有所帮助。

    1、下载完整的官网包,这里我使用的是3.0.0版本,直接点击SDKShell.bat文件便可开启工具,主要使用gdalinfo [图片绝对路径] 查看图片的坐标信息
    在这里插入图片描述

    2、Java程序配置gdal的使用,这一步坑最多,第一个是说需要配置gdal的依赖库,报错指明找不到gdalalljni.dll,则将改文件放到jdk/bin/目录下,发现依然报错,折腾半天,把所有的dll文件全部放到jdk/bin或jdk/lib/下,最后执行,能运行成功,但是还有报错说找不到proj.db文件,琢磨许久配置PROJ.LIB的环境变量,值为D:\map\release-1911-x64-gdal-3-0-0-mapserver-7-4-0\bin\proj6\share,依据自己目录来设置,这个时候再程序才能正常运行。以下是主要的程序代码:

    • 知识要点
      • geoTransform,这是一个仿射变换六参数

        • geoTransform[0]:左上角x坐标
        • geoTransform[1]:东西方向空间分辨率
        • geoTransform[2]:x方向旋转角
        • geoTransform[3]:左上角y坐标
        • geoTransform[4]:y方向旋转角
        • geoTransform[5]:南北方向空间分辨率

        影像中任意一个像素的坐标可以用下式计算:

        在这里插入图片描述

        默认情况下,a12、a21为0。

      • 根据所下载的瓦片编号,计算出拼接后的大图对应的四个点的经纬度

      • SpatialReference坐标参考类来设置坐标信息

      • 原来使用的是4326坐标系,但是在发布图层的时候发现图片纬度被压扁,照成纬度不对位,使用gdalinfo 查看猜测是有个映射属性导致,但是设置都不见生效,后面还是使用3857坐标系,这样就需要将经纬度转成实际的距离米(m)。

    gdal.AllRegister();
    Dataset sourceDataset = gdal.Open(targetDir + level + ".tif", gdalconstConstants.GA_Update);
    setGdalOfMercator(sourceDataset, tileColRow, level);
    /**
	 * 给拼接后的图片设置位置信息,墨卡托
	 */
	public static void setGdalOfMercator(Dataset sourceDataset, TileRowColAndCount tileColRow, Integer level) {
		// 六参数信息
		double[] geoTransform = sourceDataset.GetGeoTransform();
		// 设置要裁剪的起始像元位置,以及各方向的像元数
		double startX = MercatorAndLatlonExchangeUtil.getMercatorLon((360.0 * (double) tileColRow.startCol) / (1 << level) - 180.0);
		double endX = MercatorAndLatlonExchangeUtil.getMercatorLon((360.0 * (double) (tileColRow.startCol + (tileColRow.endCol - tileColRow.startCol + 1)))
				/ (1 << level) - 180.0);
		double startLat = (360 * Math.atan(Math.pow(Math.E,
				(1 - Math.pow(2, 1.0 - level) * (tileColRow.startRow - (tileColRow.endRow - tileColRow.startRow + 1)
						+ sourceDataset.GetRasterBand(1).getYSize() / 256.0)) * Math.PI)))
				/ Math.PI - 90;
		double endLat = (360 * Math.atan(Math.pow(Math.E,
				(1 - Math.pow(2, 1.0 - level)
						* (tileColRow.startRow + sourceDataset.GetRasterBand(1).getYSize() / 256.0)) * Math.PI)))
				/ Math.PI - 90;
		// 转成墨卡托坐标
		double startY = MercatorAndLatlonExchangeUtil.getMercatorLat(startLat);
		double endY = MercatorAndLatlonExchangeUtil.getMercatorLat(endLat);
		// 图片的像素大小
		int clipX = sourceDataset.GetRasterBand(1).getXSize();
		int clipY = sourceDataset.GetRasterBand(1).getYSize();
		// 左上角起始点
		geoTransform[0] = startX;
		geoTransform[3] = startY;
		// 设置空间分辨率
		geoTransform[1] = (endX - startX) / clipX;
		geoTransform[5] = (endY - startY) / clipY;
		// 偏率
		geoTransform[2] = 0.0;
		geoTransform[4] = 0.0;
		SpatialReference ref = new SpatialReference();
		ref.ImportFromEPSG(3857);
		sourceDataset.SetGeoTransform(geoTransform);
		sourceDataset.SetSpatialRef(ref);

		// 释放资源
		sourceDataset.delete();

	}
  • 不出意外,拼接后的图片用gdalinfo验证得出如下信息:
    在这里插入图片描述
将程序打包
  • 需将gdal的dll跟打包的jar放于同一目录下,设置PROJ.LIB的环境变量,值为D:\map\release-1911-x64-gdal-3-0-0-mapserver-7-4-0\bin\proj6\share,不想设置那么麻烦,我把proj6文件也放到jar所在目录,做了一个bat文件启动,downloadconfig.properties配置文件放于当前目录的上一级目录
@echo off

set SDK_ROOT=%~dp0
set SDK_ROOT=%SDK_ROOT:\\=\%

:setenv
SET "PROJ_LIB=%SDK_ROOT%proj6\SHARE"

java -jar ./downloadTileUtil.core-1.0.0-jar-with-dependencies.jar ../downloadconfig.properties

@pause
  • 将jai_imageio-1.1.jar包放置jre/lib/ext/下,这是因为程序中生成tiff文件的时候用到这些依赖,而执行jar的运行环境没有这个jar包,所以需要单独添加jar包到运行环境中(有可能需要三个jai类型的jar),不然打包出来的程序运行拼接的时候会报错
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值