golang读写和操作OpenStreetMap数据及API的插件库osm的使用

golang读写和操作OpenStreetMap数据及API的插件库osm的使用

概述

这是一个用于在Go语言中读取、写入和处理OpenStreetMap数据的通用库。主要功能包括:

  • 读写OSM XML格式
  • 读写OSM JSON格式(Overpass API返回的格式)
  • 高效解析planet.osm.org提供的OSM PBF数据文件

核心数据类型

库中提供了以下核心OSM数据类型:

  • Node(节点)
  • Way(路径)
  • Relation(关系)
  • Changeset(变更集)
  • Note(注释)
  • User(用户)

以及以下"容器"类型:

  • OSM - API返回的容器
  • Change - 复制API使用
  • Diff - 对应Overpass Augmented Diffs

子包工具

  • annotate - 为路径和关系成员添加经纬度、版本、变更集和方向数据
  • osmapi - 支持所有v0.6读取/数据端点
  • osmgeojson - OSM到GeoJSON的转换
  • osmpbf - 流式处理*.osm.pbf文件
  • osmxml - 流式处理*.osm xml文件
  • replication - 获取复制状态和变更文件

概念

  • Objects:核心OSM数据类型(Node, Way, Relation等)都实现了osm.Object接口
  • Elements:核心OSM地图数据类型的单个版本
  • Features:特定Node/Way/Relation的所有版本集合

扫描大型数据文件

对于小数据,可以使用Go标准库的encoding/xml包。对于大数据,库定义了Scanner接口:

type osm.Scanner interface {
	Scan() bool
	Object() osm.Object
	Err() error
	Close() error
}

使用示例:

f, err := os.Open("./delaware-latest.osm.pbf")
if err != nil {
	panic(err)
}
defer f.Close()

scanner := osmpbf.New(context.Background(), f, 3)
defer scanner.Close()

for scanner.Scan() {
	o := scanner.Object()
	// 处理对象
}

scanErr := scanner.Err()
if scanErr != nil {
	panic(scanErr)
}

注意:扫描器是线程安全的。应该将对象送入通道,然后让工作线程从中读取。

处理JSON

库支持读写OSM JSON格式。如果性能很重要,可以支持第三方"encoding/json"替代品,如github.com/json-iterator/go

启用方式:

import (
  jsoniter "github.com/json-iterator/go"
  "github.com/paulmach/osm"
)

var c = jsoniter.Config{
  EscapeHTML:              true,
  SortMapKeys:             false,
  MarshalFloatWith6Digits: true,
}.Froze()

osm.CustomJSONMarshaler = c
osm.CustomJSONUnmarshaler = c

CGO和zlib

OSM PBF数据以块形式存储,每个块都是zlib压缩的。解压这些数据约占读取总时间的33%。使用DataDog/czlib来加速此过程。

因此,安装此模块需要C编译器。在macOS上可能需要使用brew install pkg-config安装pkg-config。

可以使用CGO_ENABLED环境变量在构建时禁用CGO,例如CGO_ENABLED=0 go build。代码将回退到zlib的标准库实现。


更多关于golang读写和操作OpenStreetMap数据及API的插件库osm的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang读写和操作OpenStreetMap数据及API的插件库osm的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用Go操作OpenStreetMap数据及API

OpenStreetMap(OSM)是一个开源的地图数据项目,Go语言中有几个优秀的库可以帮助我们读写和操作OSM数据。下面我将介绍如何使用osm库(https://github.com/paulmach/osm)来处理OSM数据。

安装osm库

首先安装osm库:

go get github.com/paulmach/osm

基本数据结构

OSM数据主要由三种基本元素组成:

  • 节点(Node): 地图上的点,有经纬度坐标
  • 路径(Way): 由节点组成的线
  • 关系(Relation): 节点、路径和其他关系的集合
import "github.com/paulmach/osm"

读取OSM数据

从XML文件读取

func readOSMFromFile(filename string) (*osm.OSM, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    // 解码XML格式的OSM数据
    o, err := osm.Decode(file)
    if err != nil {
        return nil, err
    }

    return o, nil
}

从Overpass API获取数据

import "github.com/paulmach/osm/osmapi"

func getOSMFromAPI(bbox *osm.Bounds) (*osm.OSM, error) {
    // 创建Overpass API客户端
    client := osmapi.DefaultClient
    
    // 获取边界框内的数据
    o, err := client.Map(bbox)
    if err != nil {
        return nil, err
    }
    
    return o, nil
}

// 使用示例
func exampleAPIRequest() {
    // 定义边界框(最小经度,最小纬度,最大经度,最大纬度)
    bbox := &osm.Bounds{
        MinLon: -74.025, 
        MinLat: 40.70,
        MaxLon: -73.975,
        MaxLat: 40.75,
    }
    
    data, err := getOSMFromAPI(bbox)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("获取了%d个节点\n", len(data.Nodes))
}

处理OSM数据

遍历元素

func processOSMData(o *osm.OSM) {
    // 遍历所有节点
    for _, node := range o.Nodes {
        fmt.Printf("节点ID: %d, 位置: %f,%f\n", 
            node.ID, node.Lat, node.Lon)
            
        // 访问标签
        for k, v := range node.Tags {
            fmt.Printf("  标签: %s=%s\n", k, v)
        }
    }
    
    // 遍历所有路径
    for _, way := range o.Ways {
        fmt.Printf("路径ID: %d, 包含节点数: %d\n", 
            way.ID, len(way.Nodes))
    }
}

查询特定元素

func findElementByID(o *osm.OSM, id osm.NodeID) *osm.Node {
    for _, node := range o.Nodes {
        if node.ID == id {
            return node
        }
    }
    return nil
}

// 更高效的方式是使用osm.OSM的索引功能
func findElementEfficiently(o *osm.OSM, id osm.NodeID) *osm.Node {
    o.EnsureIndex() // 确保建立了索引
    return o.Nodes.Find(id)
}

创建和修改OSM数据

创建新节点

func createNewNode(lat, lon float64, tags map[string]string) *osm.Node {
    return &osm.Node{
        ID:   -1, // 负ID表示新元素,上传时会分配正式ID
        Lat:  lat,
        Lon:  lon,
        Tags: tags,
    }
}

修改现有元素

func modifyNode(node *osm.Node, newTags map[string]string) {
    // 更新标签
    for k, v := range newTags {
        node.Tags[k] = v
    }
    
    // 标记为已修改(如果计划上传更改)
    node.Version++
}

写入OSM数据

写入XML文件

func writeOSMToFile(o *osm.OSM, filename string) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // 编码为XML格式
    encoder := osm.NewXMLEncoder(file)
    if err := encoder.Encode(o); err != nil {
        return err
    }

    return nil
}

高级功能

地理空间查询

import "github.com/paulmach/osm/osmgeojson"

func convertToGeoJSON(o *osm.OSM) ([]byte, error) {
    // 将OSM数据转换为GeoJSON格式
    fc, err := osmgeojson.Convert(o)
    if err != nil {
        return nil, err
    }
    
    return json.Marshal(fc)
}

// 使用示例
func exampleGeoJSON() {
    data, _ := getOSMFromAPI(/*...*/)
    geojson, _ := convertToGeoJSON(data)
    fmt.Println(string(geojson))
}

使用变更集上传数据

func uploadChanges(changes *osm.OSM, username, password string) error {
    client := osmapi.DefaultClient
    client.Auth = osmapi.BasicAuth{
        Username: username,
        Password: password,
    }
    
    // 创建变更集
    changeset := &osm.Changeset{
        Tags: osm.Tags{
            "comment": "添加新建筑",
            "created_by": "My Go App",
        },
    }
    
    changeset, err := client.ChangesetCreate(changeset)
    if err != nil {
        return err
    }
    
    // 上传变更
    err = client.ChangesetUpload(changeset.ID, changes)
    if err != nil {
        return err
    }
    
    // 关闭变更集
    return client.ChangesetClose(changeset.ID)
}

性能优化

对于大型数据集,可以使用流式处理:

func processLargeOSMFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()
    
    scanner := osm.NewScanner(file)
    for scanner.Scan() {
        o := scanner.Object()
        
        switch obj := o.(type) {
        case *osm.Node:
            // 处理节点
        case *osm.Way:
            // 处理路径
        case *osm.Relation:
            // 处理关系
        }
    }
    
    return scanner.Err()
}

总结

osm库提供了完整的工具集来处理OpenStreetMap数据,包括:

  1. 读取和写入OSM XML格式
  2. 与Overpass API交互
  3. 操作OSM元素(节点、路径、关系)
  4. 转换为GeoJSON等其他格式
  5. 上传更改到OSM服务器

对于大多数OSM相关任务,这个库都能提供良好的支持。对于更高级的地理空间分析,可以结合其他GIS库如orbgeos一起使用。

记得在使用OSM API时遵守OSM使用政策,特别是关于数据下载频率的限制。

回到顶部