導入GeoJSON數據到SQL Server數據庫中


導入GeoJSON數據到SQL Server數據庫中

GeoJSON是GIS行業里一種常見的數據交換格式,能夠存儲結構化的空間地理信息。因為SQL Server從2008版開始提供了空間數據類型geometry與geography的支持,所以我也試着將項目中用到的地圖數據轉換到數據庫中,方便之后的調用。因為中途遇到了不少坑,所以寫了這篇文章作為備忘。

事前准備:了解GeoJSON與SQL Server的空間數據結構

在轉換前,我們需要對GeoJSON與SQL Server的空間數據結構有所了解。

下面給出了一篇GeoJSON文檔的范例。可以看到,GeoJSON就本質而言其實是我們常見的JSON格式的一種變體,只不過由於語言規范的限制,GeoJSON的結構相對固定。一個完整的GeoJSON就是一個包含類型信息type、以及對象集合features兩個成員的Javascript對象,主要空間地理數據存儲在這個對象features集合下的geometry、properties成員里。因此要將GeoJSON導入到SQL Server中,其實就是把geometry、properties兩個成員里的有效信息解析出來,插入到數據庫表中

{"type":"FeatureCollection",
  "features":[{"type":"Feature",
               "id":"56679924",
               "geometry":{"type":"Point",
                            "coordinates":[-77.0592213018017,38.90222845310455]},
               "properties":{"OBJECTID":56679924,"ID":72,
                              "ADDRESS":"Georgetown Harbor / 30th St NW",
                              "TERMINAL_NUMBER":"31215",
                              "LATITUDE":38.902221,"LONGITUDE":-77.059219,
                              "INSTALLED":"YES","LOCKED":"NO",
                              "INSTALL_DATE":"2010-10-05T13:43:00.000Z",
                              "REMOVAL_DATE":null,
                               "TEMPORARY_INSTALL":"NO",
                              "NUMBER_OF_BIKES":15,
                              "NUMBER_OF_EMPTY_DOCKS":4,
                              "X":394863.27537199,"Y":137153.4794371,
                              "SE_ANNO_CAD_DATA":null}
                }]

針對空間地理數據,MSSQL提供了geometry和geography兩種數據存儲類型,都能夠支持Point、 MultiPoint、 LineString、 CircularString、 MultiLineString、 CompoundCurve、 Polygon、 CurvePolygon、 MultiPolygon等常用的空間數據類型。這兩者非常相似,主要區別在於geometry采用(歐幾里得)平面坐標系,geography采用地理坐標系。我們要導入的數據如果是投影在平面上的,應該存儲在geometry類型里,而 GPS經緯度之類的橢圓體數據應存儲於geography類型下。

插入點數據

MSSQL從2016版開始正式原生支持JSON格式,所以如今我們可以很方便地利用openjson函數把GeoJSON導入到數據庫中。下面這塊代碼就從JSON中解析出了bikeShares中的點數據


 declare @bikeShares nvarchar(max) = 
'{"type":"FeatureCollection",
  "features":[{"type":"Feature",
               "id":"56679924",
               "geometry":{"type":"Point",
                            "coordinates":[-77.0592213018017,38.90222845310455]},
               "properties":{"OBJECTID":56679924,"ID":72,
                              "ADDRESS":"Georgetown Harbor / 30th St NW",
                              "X":394863.27537199,"Y":137153.4794371,
                              "SE_ANNO_CAD_DATA":null}
                }]}'

SELECT geography::STGeomFromText('POINT ('+long + ' ' + lat + ')', 4326),
           ObjectId
from OPENJSON(@bikeShares, '$.features') 
        WITH (
           long varchar(100) '$.geometry.coordinates[0]',
           lat varchar(100) '$.geometry.coordinates[1]',
           ObjectId int '$.properties.OBJECTID',
           Address nvarchar(200) '$.properties.ADDRESS'
)

代碼先從JSON中讀取了點的經緯度,然后將他們組合為'POINT ('+long + ' ' + lat + ')',再用geography::STGeomFromText方法轉化到EPSG4326球型坐標系下

插入線與面數據

線和面數據的解析因為結構問題,要復雜很多,琢磨了很久還沒搞定,幸好在stackoverflow上有相關的問題,抄作業了。


declare @CountiesGeoJson nvarchar(max) = '{ "type": "FeatureCollection", "name": "USCounty_Simplify_01", 
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::4269" } }, "features": [
{ "type": "Feature", "properties": { "STATEFP": "51", "COUNTYFP": "700", "COUNTYNS": "01498555", "GEOID": "51700", "NAME": "Newport News" }, "geometry": { "type": "MultiPolygon", "coordinates": [[[[-76.622252, 37.142146], [-76.577819, 37.187025], [-76.591432, 37.214721], [-76.565092, 37.220645], [-76.458516, 37.100661], [-76.435519, 37.094882], [-76.451274, 37.076878], [-76.442269, 37.018448], [-76.424757, 37.025107], [-76.387711304409194, 36.989671332859004], [-76.411768, 36.962847], [-76.428869, 36.969947], [-76.464471, 37.027547], [-76.518242, 37.055351], [-76.536875, 37.083942], [-76.564219, 37.077507], [-76.618252, 37.119347], [-76.622252, 37.142146]]]] } },
{ "type": "Feature", "properties": { "STATEFP": "51", "COUNTYFP": "610", "COUNTYNS": "01498423", "GEOID": "51610", "NAME": "Falls Church" }, "geometry": { "type": "MultiPolygon", "coordinates": [[[[-77.194712, 38.899073], [-77.172276, 38.893245], [-77.149701, 38.87567], [-77.189719, 38.87801], [-77.194712, 38.899073]]]] } }
]}';

select GEOID, GNAME, STATEFP, COUNTYFP, Geo=fixed
from openjson (@CountiesGeoJson, '$.features')
with
(
    GEOID char(5) '$.properties.GEOID',
    GNAME varchar(40) '$.properties.NAME',
    STATEFP char(2) '$.properties.STATEFP',
    COUNTYFP char(3) '$.properties.COUNTYFP',
    [type] Varchar(64) '$.geometry.type',
    [coordinates] nvarchar(max) '$.geometry.coordinates' as json
)
as GeoData
OUTER APPLY (
select 
   stuff( 
      (
        select concat(',  ', json_value(Value,'$[0]'),' ',json_value(Value,'$[1]'))  
        from openjson(GeoData.coordinates,'$[0]') 
        order by cast([key] as int)
        for xml path('')
      ),1,3,'') [path]
      WHERE GeoData.[type] = 'Polygon'
) PolygonData
OUTER APPLY (
    SELECT  STUFF(
        (
            SELECT CONCAT(',  ', polygon)
            FROM OPENJSON(GeoData.coordinates) as Poly 
            CROSS APPLY OPENJSON(Poly.value) as Shape 
            CROSS APPLY (
                SELECT '(' + stuff( 
                (
                    select concat(',  ', json_value(Value,'$[0]'),' ',json_value(Value,'$[1]'))  
                    from OPENJSON(Shape.value)
                    order by cast([key] as int)
                    for xml path('')
                ),1,3,'')+')' polygon
        ) Polygons
        for xml path('')
    ),1,3,'') multi
    WHERE GeoData.[type] = 'MultiPolygon'
) MultigonData
cross apply (
    SELECT concat(upper(GeoData.[type]),'((',COALESCE(PolygonData.path, MultigonData.multi),'))') WKT
) shapeDef
outer apply (
    select ID = Substring(name, CharIndex('::', name) + 2, LEN(name) - CharIndex('::', name)) from  openjson (@CountiesGeoJson, '$.crs.properties')
    with ( name varchar(100) '$.name')
) SRID
outer apply (
    select geography::STGeomFromText(WKT,IsNull(SRID.ID, 4326)).MakeValid()/*.ReorientObject()*/ as geom
) geography
outer apply (
    select CASE WHEN geom.EnvelopeAngle() > 90 THEN geom.ReorientObject() ELSE geom END as fixed
) fixes

資料來源

  1. https://docs.microsoft.com/zh-cn/archive/blogs/sqlserverstorageengine/loading-geojson-data-into-sql-server
  2. https://stackoverflow.com/questions/56371128/issue-on-trying-to-query-geojson-multipolygons-in-sql-server-2016


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM