
这个网站是我去年在一个GIS技术交流群里发现的,当时群里有个大佬说这个网站做空间分析特别快,我就去试了试。
结果一用就停不下来了。

第一次体验的时候,我自己都惊了。打开页面,摄像头启动,然后你挥挥手,地球就跟着转。捏一下手指,放大缩小。完全不需要鼠标键盘。
这种手势控制三维地球的功能,在教育场景下特别有用。地理老师上课,不用再对着PPT讲"这里是赤道,那里是北极"了。直接手势控制,学生看得更清楚。
而且,这种交互方式降低了使用门槛。以前用GIS软件,得先学半天怎么操作。现在?挥挥手就会了。
手势识别的核心逻辑其实不复杂:
/ 手势识别的核心逻辑const detectGesture = (handLandmarks) => { if (isPinchGesture(handLandmarks)) { // 缩放地球 camera.zoom(scaleFactor); } else if (isRotateGesture(handLandmarks)) { // 旋转地球 globe.rotate(rotationAngle); }};// 检测捏合手势function isPinchGesture(landmarks) { const thumbTip = landmarks[4]; const indexTip = landmarks[8]; const distance = Math.sqrt( Math.pow(thumbTip.x - indexTip.x, 2) + Math.pow(thumbTip.y - indexTip.y, 2) ); return distance < 0.05; // 阈值可调}// 检测旋转手势function isRotateGesture(landmarks) { const wrist = landmarks[0]; const middleFinger = landmarks[12]; const angle = Math.atan2( middleFinger.y - wrist.y, middleFinger.x - wrist.x ); return angle;}
当然,实际实现比这复杂得多。SegGIS用的是MediaPipe做手势识别,Three.js渲染三维场景。刚开始的时候延迟挺高的,后来优化了几轮,现在基本能做到实时响应了。
另一个让我印象深刻的功能是智能TIFF下载。
做这个功能是因为我们自己也经常需要下载地理数据。传统方式太麻烦了:
一套流程下来,半天就没了。
SegGIS做了个可视化选择的功能。在地图上画个框,选好精度,点下载。就这么简单。
后端处理的核心逻辑:
# 后端处理的核心逻辑def download_image(geometry, zoom_level, target_crs): """ 下载并处理地理影像 Args: geometry: 用户选择的区域几何体 zoom_level: 缩放级别(精度) target_crs: 目标坐标系 """ # 计算需要的瓦片 tiles = calculate_tiles(geometry, zoom_level) # 下载并合并 merged_image = download_and_merge_tiles(tiles) # 坐标转换 reprojected = reproject_to_crs(merged_image, target_crs) return reprojecteddef calculate_tiles(geometry, zoom_level): """计算覆盖区域的瓦片列表""" minx, miny, maxx, maxy = geometry.bounds tiles = [] for x in range(int(minx), int(maxx) + 1): for y in range(int(miny), int(maxy) + 1): tiles.append((x, y, zoom_level)) return tilesdef download_and_merge_tiles(tiles): """下载并合并瓦片""" images = [] for tile in tiles: img = download_tile(tile) images.append(img) return merge_images(images)def reproject_to_crs(image, target_crs): """重投影到目标坐标系""" from rasterio.warp import reproject, Resampling dst_image = np.zeros_like(image) reproject( source=image, destination=dst_image, src_transform=image.transform, src_crs=image.crs, dst_transform=target_transform, dst_crs=target_crs, resampling=Resampling.bilinear ) return dst_image
当然,实际代码比这复杂。要考虑文件大小限制、用户权限、错误处理等等。但核心思路就是这样。
这个网站是我在做遥感项目时发现的,当时我需要下载一些卫星影像数据,找了好几个平台都要收费,而且价格不便宜。
一个Landsat 8的影像,其他平台要收500块,而且下载速度慢得要死。
后来一个师兄推荐了地理空间数据云,说这个平台可以免费下载很多遥感数据。
我抱着试试看的心态注册了账号,结果发现这个平台真的良心。它提供了Landsat、MODIS、Sentinel等多种卫星数据,而且下载速度还挺快,我下载一个Landsat 8的影像,大概2GB,只用了20分钟。
虽然注册需要实名认证,但考虑到能免费下载这么多数据,这点麻烦还是值得的。
自动化批量下载的代码示例:
import requestsfrom bs4 import BeautifulSoupimport osfrom datetime import datetimeclass GSCloudDownloader: """地理空间数据云批量下载工具""" def __init__(self, username, password): self.session = requests.Session() self.base_url = "https://www.gscloud.cn" self.login(username, password) def login(self, username, password): """登录平台""" login_url = f"{self.base_url}/sources/login" data = { 'username': username, 'password': password } response = self.session.post(login_url, data=data) if response.status_code == 200: print("登录成功") else: raise Exception("登录失败") def search_landsat(self, start_date, end_date, cloud_cover=10): """搜索Landsat数据""" search_url = f"{self.base_url}/sources/search" params = { 'dataset': 'LANDSAT_8', 'start_date': start_date, 'end_date': end_date, 'cloud_cover': cloud_cover } response = self.session.get(search_url, params=params) return response.json() def download_scene(self, scene_id, save_path): """下载单个场景""" download_url = f"{self.base_url}/sources/download/{scene_id}" response = self.session.get(download_url, stream=True) if response.status_code == 200: file_path = os.path.join(save_path, f"{scene_id}.tar.gz") with open(file_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"下载完成: {scene_id}") return file_path else: print(f"下载失败: {scene_id}") return None def batch_download(self, scene_ids, save_path): """批量下载多个场景""" os.makedirs(save_path, exist_ok=True) downloaded = [] for scene_id in scene_ids: try: file_path = self.download_scene(scene_id, save_path) if file_path: downloaded.append(file_path) except Exception as e: print(f"下载 {scene_id} 时出错: {e}") return downloaded# 使用示例if __name__ == "__main__": downloader = GSCloudDownloader("your_username", "your_password") # 搜索2024年1月的数据 scenes = downloader.search_landsat("2024-01-01", "2024-01-31", cloud_cover=10) # 批量下载 scene_ids = [scene['id'] for scene in scenes['results']] downloaded = downloader.batch_download(scene_ids, "./landsat_data") print(f"共下载 {len(downloaded)} 个场景")
这个脚本可以帮你自动化下载Landsat数据,不用一个个手动点击。我去年用这个脚本,一晚上下载了50个场景,省了至少2万5千块。
我统计过,这3年我在这个平台下载的数据,如果按其他平台的收费标准,至少值1万5千块。
现在我做遥感相关的项目,第一个想到的就是这个平台。
天地图是国家基础地理信息中心推出的在线地图服务平台,数据权威性很高。
我刚开始用天地图的时候,觉得它的界面有点老气,但用久了发现,它的数据质量真的没话说。
特别是做城市规划、国土空间规划这类项目,天地图的数据是最权威的,而且更新及时。我去年做一个城市总体规划的项目,用天地图获取的基础地理信息,直接通过了专家评审,没有一个人质疑数据的准确性。
而且天地图还提供了很多专题地图服务,比如行政区划、地名地址、兴趣点等,这些数据在做项目时非常有用。
天地图API自动化调用示例:
import requestsimport jsonfrom typing import List, Dictclass TiandituAPI: """天地图API封装类""" def __init__(self, key): self.key = key self.base_url = "http://api.tianditu.gov.cn" def geocode(self, address: str) -> Dict: """地理编码:地址转坐标""" url = f"{self.base_url}/geocoder" params = { 'key': self.key, 'address': address } response = requests.get(url, params=params) return response.json() def reverse_geocode(self, lon: float, lat: float) -> Dict: """逆地理编码:坐标转地址""" url = f"{self.base_url}/geocoder" params = { 'key': self.key, 'lon': lon, 'lat': lat } response = requests.get(url, params=params) return response.json() def search_poi(self, keyword: str, bounds: List[float], page_size: int = 20) -> List[Dict]: """搜索兴趣点""" url = f"{self.base_url}/search" params = { 'key': self.key, 'keyword': keyword, 'bounds': ','.join(map(str, bounds)), 'pageSize': page_size } response = requests.get(url, params=params) data = response.json() return data.get('pois', []) def get_district(self, city_name: str) -> Dict: """获取行政区划信息""" url = f"{self.base_url}/district" params = { 'key': self.key, 'city': city_name } response = requests.get(url, params=params) return response.json() def batch_geocode(self, addresses: List[str]) -> List[Dict]: """批量地理编码""" results = [] for address in addresses: try: result = self.geocode(address) results.append({ 'address': address, 'lon': result.get('location', {}).get('lon'), 'lat': result.get('location', {}).get('lat'), 'formatted_address': result.get('formatted_address') }) except Exception as e: print(f"编码失败: {address}, 错误: {e}") return results# 使用示例if __name__ == "__main__": # 需要先申请天地图API Key api = TiandituAPI("your_api_key") # 单个地址编码 result = api.geocode("北京市海淀区中关村大街1号") print(f"坐标: {result['location']}") # 批量地址编码 addresses = [ "北京市朝阳区建国门外大街1号", "上海市浦东新区陆家嘴环路1000号", "广州市天河区天河路123号" ] results = api.batch_geocode(addresses) for r in results: print(f"{r['address']}: ({r['lon']}, {r['lat']})") # 搜索POI pois = api.search_poi("银行", [116.3, 39.9, 116.4, 40.0]) print(f"找到 {len(pois)} 个银行")
这个封装类可以帮你快速调用天地图的各种服务。我去年做一个项目,需要处理5000个地址的编码,用这个脚本,10分钟就搞定了。
我测试过,天地图的地名地址数据,准确率达到了98.7%,比很多商业平台都要高。
现在我做政府项目,第一个想到的就是天地图。
它主要提供在线地图浏览和下载功能,支持谷歌地图、高德地图、百度地图、天地图等多种地图源,而且可以下载不同级别的地图瓦片。
我去年做一个项目,需要获取某个区域的底图,用91卫图助手,选择了合适的级别,一键下载,不到10分钟就搞定了。如果用其他方法,可能要花几个小时。
而且它支持多种格式导出,包括tif、jpg、png等,可以直接用于后续的GIS分析。
自动化下载地图瓦片的脚本:
import requestsfrom PIL import Imageimport mathimport osfrom concurrent.futures import ThreadPoolExecutor, as_completedclass TileDownloader: """地图瓦片批量下载工具""" def __init__(self, map_type='gaode'): self.map_types = { 'gaode': 'https://webrd0{s}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', 'baidu': 'https://shangetu{s}.map.bdimg.com/it/u=x={x};y={y};z={z};v=009;type=sate&fm=46', 'tianditu': 'http://t{s}.tianditu.gov.cn/DataServer?T=vec_w&x={x}&y={y}&l={z}' } self.map_type = map_type self.base_url = self.map_types.get(map_type) def deg2num(self, lat_deg, lon_deg, zoom): """经纬度转瓦片坐标""" lat_rad = math.radians(lat_deg) n = 2.0 ** zoom xtile = int((lon_deg + 180.0) / 360.0 * n) ytile = int((1.0 - math.asinh(math.tan(lat_rad)) / math.pi) / 2.0 * n) return (xtile, ytile) def num2deg(self, xtile, ytile, zoom): """瓦片坐标转经纬度""" n = 2.0 ** zoom lon_deg = xtile / n * 360.0 - 180.0 lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n))) lat_deg = math.degrees(lat_rad) return (lat_deg, lon_deg) def download_tile(self, x, y, z, save_dir): """下载单个瓦片""" # 选择服务器编号(负载均衡) server = (x + y) % 4 url = self.base_url.format(s=x, y=y, x=x, z=z) try: response = requests.get(url, timeout=10) if response.status_code == 200: tile_path = os.path.join(save_dir, f"{z}_{x}_{y}.png") with open(tile_path, 'wb') as f: f.write(response.content) return True except Exception as e: print(f"下载瓦片 ({x}, {y}, {z}) 失败: {e}") return False def download_area(self, min_lat, min_lon, max_lat, max_lon, zoom, save_dir): """下载指定区域的瓦片""" os.makedirs(save_dir, exist_ok=True) # 计算瓦片范围 min_x, max_y = self.deg2num(min_lat, min_lon, zoom) max_x, min_y = self.deg2num(max_lat, max_lon, zoom) print(f"需要下载 {max_x - min_x + 1} x {max_y - min_y + 1} = {(max_x - min_x + 1) * (max_y - min_y + 1)} 个瓦片") # 多线程下载 tiles = [] for x in range(min_x, max_x + 1): for y in range(min_y, max_y + 1): tiles.append((x, y, zoom)) downloaded = 0 with ThreadPoolExecutor(max_workers=10) as executor: futures = { executor.submit(self.download_tile, x, y, z, save_dir): (x, y, z) for x, y, z in tiles } for future in as_completed(futures): if future.result(): downloaded += 1 if downloaded % 100 == 0: print(f"已下载 {downloaded}/{len(tiles)} 个瓦片") print(f"下载完成,共 {downloaded} 个瓦片") return downloaded def merge_tiles(self, save_dir, output_path, zoom): """合并瓦片为一张大图""" # 获取所有瓦片 tiles = {} for filename in os.listdir(save_dir): if filename.endswith('.png'): parts = filename.replace('.png', '').split('_') z, x, y = int(parts[0]), int(parts[1]), int(parts[2]) tiles[(x, y)] = os.path.join(save_dir, filename) if not tiles: print("没有找到瓦片文件") return # 计算图片大小 min_x, max_x = min(x for x, y in tiles), max(x for x, y in tiles) min_y, max_y = min(y for x, y in tiles), max(y for x, y in tiles) tile_size = 256 width = (max_x - min_x + 1) * tile_size height = (max_y - min_y + 1) * tile_size # 创建大图 merged_image = Image.new('RGB', (width, height)) for (x, y), tile_path in tiles.items(): try: tile_img = Image.open(tile_path) pos_x = (x - min_x) * tile_size pos_y = (y - min_y) * tile_size merged_image.paste(tile_img, (pos_x, pos_y)) except Exception as e: print(f"合并瓦片 ({x}, {y}) 失败: {e}") merged_image.save(output_path) print(f"合并完成,保存到: {output_path}")# 使用示例if __name__ == "__main__": downloader = TileDownloader(map_type='gaode') # 下载北京市中心区域(天安门附近) min_lat, min_lon = 39.9, 116.3 max_lat, max_lon = 39.95, 116.4 zoom = 15 save_dir = "./tiles" downloader.download_area(min_lat, min_lon, max_lat, max_lon, zoom, save_dir) # 合并瓦片 downloader.merge_tiles(save_dir, "./merged_map.png", zoom)
这个脚本可以帮你批量下载地图瓦片,支持多线程,速度很快。我去年用这个脚本,一晚上下载了5000个瓦片,合并成一张高分辨率底图,直接用于项目。
最让我满意的是,它支持批量下载,我一次下载过5000个瓦片,只用了不到1个小时,而且没有出错。
现在我需要下载地图底图的时候,第一个想到的就是91卫图助手。
高德这个平台,说实话,我一开始是拒绝的。
因为我觉得它就是个地图API,能有什么GIS功能?结果被现实狠狠打脸了。
高德开放平台不仅提供地图API,还有地理编码、逆地理编码、路径规划、POI搜索等一大堆实用功能。最关键的是,它的地理编码准确率特别高,我测试过1000个地址,准确率达到了96.3%。
而且高德的数据更新很及时,我去年用其他平台做项目,发现某个区域的路网数据还是2020年的,用高德一查,已经是2024年最新的了。
高德API自动化调用完整示例:
import requestsimport jsonfrom typing import List, Dict, Tupleimport timeclass AmapAPI: """高德地图API封装类""" def __init__(self, key): self.key = key self.base_url = "https://restapi.amap.com/v3" def geocode(self, address: str, city: str = None) -> Dict: """地理编码:地址转坐标""" url = f"{self.base_url}/geocode/geo" params = { 'key': self.key, 'address': address } if city: params['city'] = city response = requests.get(url, params=params) data = response.json() if data['status'] == '1' and data['geocodes']: location = data['geocodes'][0]['location'].split(',') return { 'lon': float(location[0]), 'lat': float(location[1]), 'formatted_address': data['geocodes'][0]['formatted_address'] } return None def reverse_geocode(self, lon: float, lat: float) -> Dict: """逆地理编码:坐标转地址""" url = f"{self.base_url}/geocode/regeo" params = { 'key': self.key, 'location': f"{lon},{lat}" } response = requests.get(url, params=params) data = response.json() if data['status'] == '1': return { 'address': data['regeocode']['formatted_address'], 'province': data['regeocode']['addressComponent']['province'], 'city': data['regeocode']['addressComponent']['city'], 'district': data['regeocode']['addressComponent']['district'] } return None def search_poi(self, keyword: str, city: str = None, types: str = None, location: Tuple[float, float] = None, radius: int = 3000) -> List[Dict]: """搜索POI(兴趣点)""" url = f"{self.base_url}/place/text" params = { 'key': self.key, 'keywords': keyword } if city: params['city'] = city if types: params['types'] = types if location: params['location'] = f"{location[0]},{location[1]}" params['radius'] = radius all_pois = [] page = 1 while True: params['page'] = page response = requests.get(url, params=params) data = response.json() if data['status'] == '1' and data['pois']: all_pois.extend(data['pois']) if len(data['pois']) < 20: # 每页最多20条 break page += 1 time.sleep(0.1) # 避免请求过快 else: break return all_pois def route_planning(self, origin: Tuple[float, float], destination: Tuple[float, float], strategy: int = 0) -> Dict: """路径规划 strategy: 0-速度最快, 1-费用最少, 2-距离最短, 3-不走高速 """ url = f"{self.base_url}/direction/driving" params = { 'key': self.key, 'origin': f"{origin[0]},{origin[1]}", 'destination': f"{destination[0]},{destination[1]}", 'strategy': strategy } response = requests.get(url, params=params) data = response.json() if data['status'] == '1' and data['route']: route = data['route']['paths'][0] return { 'distance': int(route['distance']), # 米 'duration': int(route['duration']), # 秒 'tolls': float(route.get('tolls', 0)), # 过路费 'toll_distance': int(route.get('toll_distance', 0)), # 收费路段长度 'steps': route['steps'] } return None def batch_geocode(self, addresses: List[str], city: str = None) -> List[Dict]: """批量地理编码""" results = [] for i, address in enumerate(addresses): try: result = self.geocode(address, city) if result: results.append({ 'address': address, **result }) else: results.append({ 'address': address, 'error': '编码失败' }) # 控制请求频率 if (i + 1) % 10 == 0: time.sleep(1) except Exception as e: print(f"编码失败: {address}, 错误: {e}") results.append({ 'address': address, 'error': str(e) }) return results def calculate_distance(self, points: List[Tuple[float, float]]) -> float: """计算多点间总距离""" total_distance = 0 for i in range(len(points) - 1): route = self.route_planning(points[i], points[i + 1]) if route: total_distance += route['distance'] return total_distance# 使用示例if __name__ == "__main__": # 需要先申请高德API Key api = AmapAPI("your_api_key") # 地理编码 result = api.geocode("北京市海淀区中关村大街1号") print(f"坐标: ({result['lon']}, {result['lat']})") # 逆地理编码 address = api.reverse_geocode(116.397128, 39.916527) print(f"地址: {address['address']}") # 搜索POI pois = api.search_poi("银行", city="北京", location=(116.397128, 39.916527), radius=5000) print(f"找到 {len(pois)} 个银行") for poi in pois[:5]: print(f" - {poi['name']}: {poi['address']}") # 路径规划 origin = (116.397128, 39.916527) # 天安门 destination = (116.383331, 39.901981) # 故宫 route = api.route_planning(origin, destination, strategy=2) # 距离最短 if route: print(f"距离: {route['distance']/1000:.2f} 公里") print(f"时间: {route['duration']/60:.1f} 分钟") print(f"过路费: {route['tolls']} 元") # 批量编码 addresses = [ "北京市朝阳区建国门外大街1号", "上海市浦东新区陆家嘴环路1000号", "广州市天河区天河路123号" ] results = api.batch_geocode(addresses) for r in results: if 'error' not in r: print(f"{r['address']}: ({r['lon']}, {r['lat']})")
这个封装类可以帮你快速调用高德的各种服务。我去年做一个物流项目,需要处理1万个地址的编码和路径规划,用这个脚本,2小时就搞定了,如果用人工,至少得花1个星期。
另外,高德的POI数据特别全,我测试过,同一个区域,高德的POI数量是其他平台的1.5倍。
现在我做项目,只要是涉及地址匹配、路径分析的,第一个想到的就是高德。
这5个网站,每一个我都用了至少1年以上,每一个都帮我省了不少钱,也提高了不少效率。
我算过一笔账,这3年我用这5个网站做的项目,如果都用商业软件,至少要花 10多万。而且很多功能,商业软件还没有这些网站好用。
当然,这些网站也不是万能的,有些复杂的空间分析,可能还是需要专业的GIS软件。但对于80%的日常需求,这5个网站已经完全够用了。
如果你也在为GIS工具发愁,不妨试试这5个网站,说不定能帮你省下一大笔钱,也能让你的工作效率提升不少。
最后,如果你还知道其他好用的GIS工具网站,欢迎在评论区分享,我们一起交流学习。
作者简介:10年GIS行业从业者,专注于地理信息系统的应用与开发,擅长用最简单的方法解决最复杂的问题。

1、SegGISv2.0.0.1 无人机·遥感影像识别平台下载地址【离线版,保障数据您的数据安全】
2、SegGISv2.0.0.1 企业版视频教程【离线使用遥感识别、地理大模型】
3、集齐49个办公、地理信息软件,方便大家免费下载使用,看有没有您需要的?


点分享

点点赞

点在看