#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
# $Id: ogr_geojson.py e4041afa8c4a60ed29ae460fd764b20bf1a5b4a7 2018-09-17 00:07:16 +0200 Even Rouault $
#
# Project:  GDAL/OGR Test Suite
# Purpose:  GeoJSON driver test suite.
# Author:   Mateusz Loskot <mateusz@loskot.net>
#
###############################################################################
# Copyright (c) 2007, Mateusz Loskot <mateusz@loskot.net>
# Copyright (c) 2009-2014, Even Rouault <even dot rouault at mines-paris dot org>
# Copyright (c) 2013, Kyle Shannon <kyle at pobox dot com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
###############################################################################

import json
import os
import sys

sys.path.append('../pymod')

from osgeo import osr
from osgeo import ogr
from osgeo import gdal

import gdaltest
import ogrtest
###############################################################################
# Test utilities


def validate_layer(lyr, name, features, type, fields, box):

    if name is not None and name != lyr.GetName():
        print('Wrong layer name')
        return False

    if features != lyr.GetFeatureCount():
        print('Wrong number of features')
        return False

    lyrDefn = lyr.GetLayerDefn()
    if lyrDefn is None:
        print('Layer definition is none')
        return False

    if type != lyrDefn.GetGeomType():
        print('Wrong geometry type')
        print(lyrDefn.GetGeomType())
        return False

    if fields != lyrDefn.GetFieldCount():
        print('Wrong number of fields')
        return False

    extent = lyr.GetExtent()

    minx = abs(extent[0] - box[0])
    maxx = abs(extent[1] - box[1])
    miny = abs(extent[2] - box[2])
    maxy = abs(extent[3] - box[3])

    if max(minx, maxx, miny, maxy) > 0.0001:
        print('Wrong spatial extent of layer')
        print(extent)
        return False

    return True


def verify_geojson_copy(name, fids, names):

    if gdaltest.gjpoint_feat is None:
        print('Missing features collection')
        return False

    fname = os.path.join('tmp', name + '.geojson')
    ds = ogr.Open(fname)
    if ds is None:
        print('Can not open \'' + fname + '\'')
        return False

    lyr = ds.GetLayer(0)
    if lyr is None:
        print('Missing layer')
        return False

    ######################################################
    # Test attributes
    ret = ogrtest.check_features_against_list(lyr, 'FID', fids)
    if ret != 1:
        print('Wrong values in \'FID\' field')
        return False

    lyr.ResetReading()
    ret = ogrtest.check_features_against_list(lyr, 'NAME', names)
    if ret != 1:
        print('Wrong values in \'NAME\' field')
        return False

    ######################################################
    # Test geometries
    lyr.ResetReading()
    for i in range(len(gdaltest.gjpoint_feat)):

        orig_feat = gdaltest.gjpoint_feat[i]
        feat = lyr.GetNextFeature()

        if feat is None:
            print('Failed trying to read feature')
            return False

        if ogrtest.check_feature_geometry(feat, orig_feat.GetGeometryRef(),
                                          max_error=0.001) != 0:
            print('Geometry test failed')
            gdaltest.gjpoint_feat = None
            return False

    gdaltest.gjpoint_feat = None

    lyr = None

    return True


def copy_shape_to_geojson(gjname, compress=None):

    if gdaltest.geojson_drv is None:
        return False

    if compress is not None:
        if compress[0:5] == '/vsig':
            dst_name = os.path.join('/vsigzip/', 'tmp', gjname + '.geojson' + '.gz')
        elif compress[0:4] == '/vsiz':
            dst_name = os.path.join('/vsizip/', 'tmp', gjname + '.geojson' + '.zip')
        elif compress == '/vsistdout/':
            dst_name = compress
        else:
            return False
    else:
        dst_name = os.path.join('tmp', gjname + '.geojson')

    ds = gdaltest.geojson_drv.CreateDataSource(dst_name)
    if ds is None:
        return False

    ######################################################
    # Create layer
    lyr = ds.CreateLayer(gjname)
    if lyr is None:
        return False

    ######################################################
    # Setup schema (all test shapefiles use common schmea)
    ogrtest.quick_create_layer_def(lyr,
                                   [('FID', ogr.OFTReal),
                                    ('NAME', ogr.OFTString)])

    ######################################################
    # Copy in gjpoint.shp

    dst_feat = ogr.Feature(feature_def=lyr.GetLayerDefn())

    src_name = os.path.join('data', gjname + '.shp')
    shp_ds = ogr.Open(src_name)
    shp_lyr = shp_ds.GetLayer(0)

    feat = shp_lyr.GetNextFeature()
    gdaltest.gjpoint_feat = []

    while feat is not None:

        gdaltest.gjpoint_feat.append(feat)

        dst_feat.SetFrom(feat)
        lyr.CreateFeature(dst_feat)

        feat = shp_lyr.GetNextFeature()

    shp_lyr = None
    lyr = None

    ds = None

    return True

###############################################################################
# Find GeoJSON driver


def ogr_geojson_1():

    gdaltest.geojson_drv = ogr.GetDriverByName('GeoJSON')

    if gdaltest.geojson_drv is not None:
        return 'success'
    else:
        return 'fail'

###############################################################################
# Test file-based DS with standalone "Point" feature object.


def ogr_geojson_2():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/point.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('point')
    if lyr is None:
        gdaltest.post_reason('Missing layer called point')
        return 'fail'

    extent = (100.0, 100.0, 0.0, 0.0)

    rc = validate_layer(lyr, 'point', 1, ogr.wkbPoint, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

###############################################################################
# Test file-based DS with standalone "LineString" feature object.


def ogr_geojson_3():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/linestring.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('linestring')
    if lyr is None:
        gdaltest.post_reason('Missing layer called linestring')
        return 'fail'

    extent = (100.0, 101.0, 0.0, 1.0)

    rc = validate_layer(lyr, 'linestring', 1, ogr.wkbLineString, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

##############################################################################
# Test file-based DS with standalone "Polygon" feature object.


def ogr_geojson_4():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/polygon.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('polygon')
    if lyr is None:
        gdaltest.post_reason('Missing layer called polygon')
        return 'fail'

    extent = (100.0, 101.0, 0.0, 1.0)

    rc = validate_layer(lyr, 'polygon', 1, ogr.wkbPolygon, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

##############################################################################
# Test file-based DS with standalone "GeometryCollection" feature object.


def ogr_geojson_5():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/geometrycollection.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('geometrycollection')
    if lyr is None:
        gdaltest.post_reason('Missing layer called geometrycollection')
        return 'fail'

    extent = (100.0, 102.0, 0.0, 1.0)

    rc = validate_layer(lyr, 'geometrycollection', 1, ogr.wkbGeometryCollection, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

##############################################################################
# Test file-based DS with standalone "MultiPoint" feature object.


def ogr_geojson_6():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/multipoint.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('multipoint')
    if lyr is None:
        gdaltest.post_reason('Missing layer called multipoint')
        return 'fail'

    extent = (100.0, 101.0, 0.0, 1.0)

    rc = validate_layer(lyr, 'multipoint', 1, ogr.wkbMultiPoint, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

##############################################################################
# Test file-based DS with standalone "MultiLineString" feature object.


def ogr_geojson_7():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/multilinestring.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('multilinestring')
    if lyr is None:
        gdaltest.post_reason('Missing layer called multilinestring')
        return 'fail'

    extent = (100.0, 103.0, 0.0, 3.0)

    rc = validate_layer(lyr, 'multilinestring', 1, ogr.wkbMultiLineString, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

##############################################################################
# Test file-based DS with standalone "MultiPolygon" feature object.


def ogr_geojson_8():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/multipolygon.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('multipolygon')
    if lyr is None:
        gdaltest.post_reason('Missing layer called multipolygon')
        return 'fail'

    extent = (100.0, 103.0, 0.0, 3.0)

    rc = validate_layer(lyr, 'multipolygon', 1, ogr.wkbMultiPolygon, 0, extent)
    if not rc:
        return 'fail'

    lyr = None

    return 'success'

##############################################################################
# Test translation of data/gjpoint.shp to GeoJSON file


def ogr_geojson_9():

    if gdaltest.geojson_drv is None:
        return 'skip'

    gdaltest.tests = [
        ['gjpoint', [1], ['Point 1']],
        ['gjline', [1], ['Line 1']],
        ['gjpoly', [1], ['Polygon 1']],
        ['gjmultipoint', [1], ['MultiPoint 1']],
        ['gjmultiline', [2], ['MultiLine 1']],
        ['gjmultipoly', [2], ['MultiPoly 1']]
    ]

    for i in range(len(gdaltest.tests)):
        test = gdaltest.tests[i]

        rc = copy_shape_to_geojson(test[0])
        if not rc:
            gdaltest.post_reason('Failed making copy of ' + test[0] + '.shp')
            return 'fail'

        rc = verify_geojson_copy(test[0], test[1], test[2])
        if not rc:
            gdaltest.post_reason('Verification of copy of ' + test[0] + '.shp failed')
            return 'fail'

    return 'success'

##############################################################################
# Test translation of data/gjpoint.shp to GZip compressed GeoJSON file


def ogr_geojson_10():

    if gdaltest.geojson_drv is None:
        return 'skip'

    gdaltest.tests = [
        ['gjpoint', [1], ['Point 1']],
        ['gjline', [1], ['Line 1']],
        ['gjpoly', [1], ['Polygon 1']],
        ['gjmultipoint', [1], ['MultiPoint 1']],
        ['gjmultiline', [2], ['MultiLine 1']],
        ['gjmultipoly', [2], ['MultiPoly 1']]
    ]

    for i in range(len(gdaltest.tests)):
        test = gdaltest.tests[i]

        rc = copy_shape_to_geojson(test[0], '/vsigzip/')
        if not rc:
            gdaltest.post_reason('Failed making copy of ' + test[0] + '.shp')
            return 'fail'

        rc = verify_geojson_copy(test[0], test[1], test[2])
        if not rc:
            gdaltest.post_reason('Verification of copy of ' + test[0] + '.shp failed')
            return 'fail'

    return 'success'

###############################################################################


def ogr_geojson_11():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/srs_name.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('srs_name')
    if lyr is None:
        gdaltest.post_reason('Missing layer called srs_name')
        return 'fail'

    extent = (100.0, 102.0, 0.0, 1.0)

    rc = validate_layer(lyr, 'srs_name', 1, ogr.wkbGeometryCollection, 0, extent)
    if not rc:
        return 'fail'

    ref = lyr.GetSpatialRef()
    pcs = int(ref.GetAuthorityCode('PROJCS'))
    if pcs != 26915:
        gdaltest.post_reason('Spatial reference was not valid')
        return 'fail'

    feature = lyr.GetNextFeature()
    geometry = feature.GetGeometryRef().GetGeometryRef(0)

    srs = geometry.GetSpatialReference()
    pcs = int(srs.GetAuthorityCode('PROJCS'))
    if pcs != 26916:
        gdaltest.post_reason('Spatial reference for individual geometry was not valid')
        return 'fail'

    lyr = None

    return 'success'

###############################################################################
# Test DS passed as name with standalone "Point" feature object (#3377)


def ogr_geojson_12():

    if gdaltest.geojson_drv is None:
        return 'skip'

    import os
    if os.name == 'nt':
        return 'skip'

    import test_cli_utilities

    if test_cli_utilities.get_ogrinfo_path() is None:
        return 'skip'

    ret = gdaltest.runexternal(test_cli_utilities.get_ogrinfo_path() + ' -ro -al \'{"type": "Point","coordinates": [100.0, 0.0]}\'')
    if ret.find(' POINT (100 0)') == -1:
        print(ret)
        return 'fail'

    return 'success'


###############################################################################
# Test writing to stdout (#3381)

def ogr_geojson_13():

    if gdaltest.geojson_drv is None:
        return 'skip'

    test = ['gjpoint', [1], ['Point 1']]

    rc = copy_shape_to_geojson(test[0], '/vsistdout/')
    if not rc:
        gdaltest.post_reason('Failed making copy of ' + test[0] + '.shp')
        return 'fail'

    return 'success'

###############################################################################
# Test reading & writing various degenerated geometries


def ogr_geojson_14():

    if gdaltest.geojson_drv is None:
        return 'skip'

    if int(gdal.VersionInfo('VERSION_NUM')) < 1800:
        return 'skip'

    with gdaltest.error_handler():
        ds = ogr.Open('data/ogr_geojson_14.geojson')
    lyr = ds.GetLayer(0)

    out_ds = gdaltest.geojson_drv.CreateDataSource('tmp/out_ogr_geojson_14.geojson')
    out_lyr = out_ds.CreateLayer('lyr')

    with gdaltest.error_handler():
        for feat in lyr:
            geom = feat.GetGeometryRef()
            if geom is not None:
                # print(geom)
                out_feat = ogr.Feature(feature_def=out_lyr.GetLayerDefn())
                out_feat.SetGeometry(geom)
                out_lyr.CreateFeature(out_feat)

    out_ds = None

    return 'success'

###############################################################################
# Test Feature.ExportToJson (#3870)


def ogr_geojson_15():

    feature_defn = ogr.FeatureDefn()
    feature_defn.AddFieldDefn(ogr.FieldDefn("foo"))
    field_defn = ogr.FieldDefn("boolfield", ogr.OFTInteger)
    field_defn.SetSubType(ogr.OFSTBoolean)
    feature_defn.AddFieldDefn(field_defn)

    feature = ogr.Feature(feature_defn)
    feature.SetField("foo", "bar")
    feature.SetField("boolfield", True)
    feature.SetFID(0)

    geom = ogr.CreateGeometryFromWkt("POINT(1 2)")
    feature.SetGeometry(geom)

    try:
        out = feature.ExportToJson()
    except ImportError:
        return 'skip'

    expected_out = """{"geometry": {"type": "Point", "coordinates": [1.0, 2.0]}, "type": "Feature", "properties": {"foo": "bar", "boolfield": true}, "id": 0}"""

    if out != expected_out:
        out_json = json.loads(out)
        expected_out_json = json.loads(expected_out)
        if out_json != expected_out_json:
            gdaltest.post_reason('fail')
            print(out)
            return 'fail'

    out = feature.ExportToJson(as_object=True)
    expected_out = {'geometry': {'type': 'Point', 'coordinates': [1.0, 2.0]}, 'type': 'Feature', 'properties': {'foo': 'bar', "boolfield": True}, 'id': 0}

    if out != expected_out:
        gdaltest.post_reason('fail')
        print(out)
        return 'fail'

    return 'success'

###############################################################################
# Test reading ESRI point file


def ogr_geojson_16():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esripoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('esripoint')
    if lyr is None:
        gdaltest.post_reason('Missing layer called esripoint')
        return 'fail'

    extent = (2, 2, 49, 49)

    rc = validate_layer(lyr, 'esripoint', 1, ogr.wkbPoint, 4, extent)
    if not rc:
        return 'fail'

    ref = lyr.GetSpatialRef()
    gcs = int(ref.GetAuthorityCode('GEOGCS'))

    if not gcs == 4326:
        gdaltest.post_reason("Spatial reference was not valid")
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('POINT(2 49)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFID() != 1:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFieldAsInteger('fooInt') != 2:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFieldAsDouble('fooDouble') != 3.4:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFieldAsString('fooString') != '56':
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI linestring file


def ogr_geojson_17():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrilinestring.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    extent = (2, 3, 49, 50)

    rc = validate_layer(lyr, None, 1, ogr.wkbLineString, 0, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('LINESTRING (2 49,3 50)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    # MultiLineString
    ds = ogr.Open("""{
  "geometryType": "esriGeometryPolyline",
  "fields": [],
  "features": [
  {
   "geometry": {
      "paths" : [
       [ [2,49],[2.1,49.1] ],
       [ [3,50],[3.1,50.1] ]
      ]
   }
  }
 ]
}""")
    lyr = ds.GetLayer(0)
    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTILINESTRING ((2 49,2.1 49.1),(3 50,3.1 50.1))')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    return 'success'

###############################################################################
# Test reading ESRI polygon file


def ogr_geojson_18():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esripolygon.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    extent = (-3, 3, 49, 50)

    rc = validate_layer(lyr, None, 1, ogr.wkbPolygon, 0, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTIPOLYGON (((2 49,2 50,3 50,3 49,2 49),(2.1 49.1,2.1 49.9,2.9 49.9,2.9 49.1,2.1 49.1)),((-2 49,-2 50,-3 50,-3 49,-2 49)))')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    ds = ogr.Open('data/esripolygonempty.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'
    lyr = ds.GetLayer(0)
    feature = lyr.GetNextFeature()
    if feature.GetGeometryRef().ExportToWkt() != 'POLYGON EMPTY':
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI multipoint file


def ogr_geojson_19():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrimultipoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    extent = (2, 3, 49, 50)

    rc = validate_layer(lyr, None, 1, ogr.wkbMultiPoint, 4, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTIPOINT (2 49,3 50)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading files with no extension (#4314)


def ogr_geojson_20():

    if gdaltest.geojson_drv is None:
        return 'skip'

    from glob import glob

    geojson_files = glob('data/*.json')
    geojson_files.extend(glob('data/*.geojson'))

    for gj in geojson_files:
        # create tmp file with no file extension
        data = open(gj, 'rb').read()

        f = gdal.VSIFOpenL('/vsimem/testgj', 'wb')
        gdal.VSIFWriteL(data, 1, len(data), f)
        gdal.VSIFCloseL(f)

        gdal.PushErrorHandler('CPLQuietErrorHandler')
        ds = ogr.Open('/vsimem/testgj')
        gdal.PopErrorHandler()
        if ds is None:
            print(gj)
            print(data.decode('LATIN1'))
            gdaltest.post_reason('Failed to open datasource')
            return 'fail'
        ds = None

        gdal.Unlink('/vsimem/testgj')

    return 'success'

###############################################################################
# Test reading output of geocouch spatiallist


def ogr_geojson_21():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open("""{"type": "FeatureCollection", "features":[
{"type": "Feature",
 "geometry": {"type":"Point","coordinates":[1,2]},
 "properties": {"_id":"aid", "_rev":"arev", "type":"Feature",
                "properties":{"intvalue" : 2, "floatvalue" : 3.2, "strvalue" : "foo", "properties": { "foo": "bar"}}}}]}""")
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayerByName('OGRGeoJSON')

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('POINT (1 2)')
    if feature.GetFieldAsString("_id") != 'aid' or \
       feature.GetFieldAsString("_rev") != 'arev' or \
       feature.GetFieldAsInteger("intvalue") != 2 or \
       ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Same as ogr_geojson_21 with several features


def ogr_geojson_22():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open("""{"type": "FeatureCollection", "features":[
{"type": "Feature",
 "geometry": {"type":"Point","coordinates":[1,2]},
 "properties": {"_id":"aid", "_rev":"arev", "type":"Feature",
                "properties":{"intvalue" : 2, "floatvalue" : 3.2, "strvalue" : "foo"}}},
{"type": "Feature",
 "geometry": {"type":"Point","coordinates":[3,4]},
 "properties": {"_id":"aid2", "_rev":"arev2", "type":"Feature",
                "properties":{"intvalue" : 3.5, "str2value" : "bar"}}}]}""")
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayerByName('OGRGeoJSON')

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('POINT (1 2)')
    if feature.GetFieldAsString("_id") != 'aid' or \
       feature.GetFieldAsString("_rev") != 'arev' or \
       feature.GetFieldAsDouble("intvalue") != 2 or \
       ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('POINT (3 4)')
    if feature.GetFieldAsString("_id") != 'aid2' or \
       feature.GetFieldAsString("_rev") != 'arev2' or \
       feature.GetFieldAsDouble("intvalue") != 3.5 or \
       feature.GetFieldAsString("str2value") != 'bar' or \
       ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Write GeoJSON with bbox and test SRS writing&reading back


def ogr_geojson_23():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = gdaltest.geojson_drv.CreateDataSource('/vsimem/ogr_geojson_23.json')
    sr = osr.SpatialReference()
    sr.ImportFromEPSG(4322)
    lyr = ds.CreateLayer('foo', srs=sr, options=['WRITE_BBOX=YES'])
    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetGeometry(ogr.CreateGeometryFromWkt('POINT(1 10)'))
    lyr.CreateFeature(feat)
    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetGeometry(ogr.CreateGeometryFromWkt('POINT(2 20)'))
    lyr.CreateFeature(feat)
    lyr = None
    ds = None

    ds = ogr.Open('/vsimem/ogr_geojson_23.json')
    lyr = ds.GetLayer(0)
    sr_got = lyr.GetSpatialRef()
    ds = None

    if sr_got.ExportToWkt() != sr.ExportToWkt():
        gdaltest.post_reason('did not get expected SRS')
        print(sr_got)
        return 'fail'

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_23.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_23.json')

    if data.find('"bbox": [ 1, 10, 2, 20 ]') == -1:
        gdaltest.post_reason('did not find global bbox')
        print(data)
        return 'fail'

    if data.find('"bbox": [ 1.0, 10.0, 1.0, 10.0 ]') == -1:
        gdaltest.post_reason('did not find first feature bbox')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test alternate form of geojson


def ogr_geojson_24():

    if gdaltest.geojson_drv is None:
        return 'skip'

    content = """loadGeoJSON({"layerFoo": { "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [2, 49]
    },
  "name": "bar"
},
"layerBar": { "type": "FeatureCollection", "features" : [  { "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [2, 49]
    },
  "other_name": "baz"
}]}})"""

    for i in range(2):
        if i == 0:
            ds = ogr.Open(content)
        else:
            gdal.FileFromMemBuffer('/vsimem/ogr_geojson_24.js', content)
            ds = ogr.Open('/vsimem/ogr_geojson_24.js')
            gdal.Unlink('/vsimem/ogr_geojson_24.js')

        if ds is None:
            gdaltest.post_reason('Failed to open datasource')
            return 'fail'

        lyr = ds.GetLayerByName('layerFoo')
        if lyr is None:
            gdaltest.post_reason('cannot find layer')
            return 'fail'

        feature = lyr.GetNextFeature()
        ref_geom = ogr.CreateGeometryFromWkt('POINT (2 49)')
        if feature.GetFieldAsString("name") != 'bar' or \
                ogrtest.check_feature_geometry(feature, ref_geom) != 0:
            feature.DumpReadable()
            return 'fail'

        lyr = ds.GetLayerByName('layerBar')
        if lyr is None:
            gdaltest.post_reason('cannot find layer')
            return 'fail'

        feature = lyr.GetNextFeature()
        ref_geom = ogr.CreateGeometryFromWkt('POINT (2 49)')
        if feature.GetFieldAsString("other_name") != 'baz' or \
                ogrtest.check_feature_geometry(feature, ref_geom) != 0:
            feature.DumpReadable()
            return 'fail'

        ds = None

    return 'success'

###############################################################################
# Test TopoJSON


def ogr_geojson_25():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/topojson1.topojson')
    lyr = ds.GetLayer(0)
    if lyr.GetName() != 'a_layer':
        gdaltest.post_reason('failure')
        return 'fail'
    feat = lyr.GetNextFeature()
    if ogrtest.check_feature_geometry(feat, 'LINESTRING (100 1000,110 1000,110 1100)') != 0:
        gdaltest.post_reason('failure')
        return 'fail'
    lyr = ds.GetLayer(1)
    if lyr.GetName() != 'TopoJSON':
        gdaltest.post_reason('failure')
        return 'fail'
    expected_results = [
        (None, None, 'POINT EMPTY'),
        (None, None, 'POINT EMPTY'),
        (None, None, 'POINT EMPTY'),
        (None, None, 'POINT (100 1010)'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, None, 'LINESTRING EMPTY'),
        (None, '0', 'LINESTRING EMPTY'),
        (None, 'foo', 'LINESTRING EMPTY'),
        ('1', None, 'LINESTRING (100 1000,110 1000,110 1100)'),
        ('2', None, 'LINESTRING (110 1100,110 1000,100 1000)'),
        (None, None, 'POLYGON EMPTY'),
        (None, None, 'POLYGON EMPTY'),
        (None, None, 'POLYGON EMPTY'),
        (None, None, 'POLYGON ((100 1000,110 1000,110 1100,100 1100,100 1000),(101 1010,101 1090,109 1090,109 1010,101 1010))'),
        (None, None, 'POLYGON ((110 1100,110 1000,100 1000,100 1100,110 1100),(101 1010,109 1010,109 1090,101 1090,101 1010))'),
        (None, None, 'MULTIPOINT EMPTY'),
        (None, None, 'MULTIPOINT EMPTY'),
        (None, None, 'MULTIPOINT EMPTY'),
        (None, None, 'MULTIPOINT EMPTY'),
        (None, None, 'MULTIPOINT (100 1010,101 1020)'),
        (None, None, 'MULTIPOLYGON EMPTY'),
        (None, None, 'MULTIPOLYGON EMPTY'),
        (None, None, 'MULTIPOLYGON EMPTY'),
        (None, None, 'MULTIPOLYGON (((110 1100,110 1000,100 1000,100 1100,110 1100)),((101 1010,109 1010,109 1090,101 1090,101 1010)))'),
        (None, None, 'MULTILINESTRING EMPTY'),
        (None, None, 'MULTILINESTRING EMPTY'),
        (None, None, 'MULTILINESTRING ((100 1000,110 1000,110 1100))'),
        (None, None, 'MULTILINESTRING ((100 1000,110 1000,110 1100,100 1100,100 1000))'),
        (None, None, 'MULTILINESTRING ((100 1000,110 1000,110 1100,100 1100,100 1000),(101 1010,101 1090,109 1090,109 1010,101 1010))'),
    ]
    if lyr.GetFeatureCount() != len(expected_results):
        gdaltest.post_reason('failure')
        return 'fail'
    for i in range(len(expected_results)):
        feat = lyr.GetNextFeature()
        if feat.GetField('id') != expected_results[i][0] or \
           feat.GetField('name') != expected_results[i][1] or \
           feat.GetGeometryRef().ExportToWkt() != expected_results[i][2]:
            gdaltest.post_reason('failure at feat index %d' % i)
            feat.DumpReadable()
            print(expected_results[i])
            print(feat.GetField('name'))
            return 'fail'
    ds = None

    ds = ogr.Open('data/topojson2.topojson')
    lyr = ds.GetLayer(0)
    if lyr.GetName() != 'a_layer':
        gdaltest.post_reason('failure')
        return 'fail'
    feat = lyr.GetNextFeature()
    if ogrtest.check_feature_geometry(feat, 'LINESTRING (100 1000,110 1000,110 1100)') != 0:
        gdaltest.post_reason('failure')
        return 'fail'
    lyr = ds.GetLayer(1)
    if lyr.GetName() != 'TopoJSON':
        gdaltest.post_reason('failure')
        return 'fail'
    feat = lyr.GetNextFeature()
    if ogrtest.check_feature_geometry(feat, 'LINESTRING (100 1000,110 1000,110 1100)') != 0:
        gdaltest.post_reason('failure')
        return 'fail'
    ds = None

    ds = ogr.Open('data/topojson3.topojson')
    lyr = ds.GetLayer(0)
    if lyr.GetName() != 'a_layer':
        gdaltest.post_reason('failure')
        return 'fail'
    feat = lyr.GetNextFeature()
    if ogrtest.check_feature_geometry(feat, 'LINESTRING (0 0,10 0,0 10,10 0,0 0)') != 0:
        gdaltest.post_reason('failure')
        return 'fail'
    lyr = ds.GetLayer(1)
    if lyr.GetName() != 'TopoJSON':
        gdaltest.post_reason('failure')
        return 'fail'
    feat = lyr.GetNextFeature()
    if ogrtest.check_feature_geometry(feat, 'LINESTRING (0 0,10 0,0 10,10 0,0 0)') != 0:
        gdaltest.post_reason('failure')
        return 'fail'
    ds = None

    return 'success'

###############################################################################
# Test 64bit support


def ogr_geojson_26():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open("""{"type": "FeatureCollection", "features":[
{"type": "Feature", "id": 1,
 "geometry": {"type":"Point","coordinates":[1,2]},
 "properties": { "intvalue" : 1, "int64" : 1234567890123, "intlist" : [1] }},
{"type": "Feature", "id": 1234567890123,
 "geometry": {"type":"Point","coordinates":[3,4]},
 "properties": { "intvalue" : 1234567890123, "intlist" : [1, 1234567890123] }},
 ]}""")
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayerByName('OGRGeoJSON')
    if lyr.GetMetadataItem(ogr.OLMD_FID64) is None:
        gdaltest.post_reason('fail')
        return 'fail'

    feature = lyr.GetNextFeature()
    if feature.GetFID() != 1:
        gdaltest.post_reason('fail')
        feature.DumpReadable()
        return 'fail'
    if feature.GetField("intvalue") != 1:
        gdaltest.post_reason('fail')
        feature.DumpReadable()
        return 'fail'
    if feature.GetField("int64") != 1234567890123:
        gdaltest.post_reason('fail')
        feature.DumpReadable()
        return 'fail'

    feature = lyr.GetNextFeature()
    if feature.GetFID() != 1234567890123:
        gdaltest.post_reason('fail')
        feature.DumpReadable()
        return 'fail'
    if feature.GetField("intvalue") != 1234567890123:
        gdaltest.post_reason('fail')
        feature.DumpReadable()
        return 'fail'
    if feature.GetField("intlist") != [1, 1234567890123]:
        gdaltest.post_reason('fail')
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    ds = gdaltest.geojson_drv.CreateDataSource('/vsimem/ogr_geojson_26.json')
    lyr = ds.CreateLayer('test')
    lyr.CreateField(ogr.FieldDefn('int64', ogr.OFTInteger64))
    lyr.CreateField(ogr.FieldDefn('int64list', ogr.OFTInteger64List))
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetFID(1234567890123)
    f.SetField(0, 1234567890123)
    f.SetFieldInteger64List(1, [1234567890123])
    lyr.CreateFeature(f)
    f = None
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_26.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_26.json')

    if data.find('{ "type": "Feature", "id": 1234567890123, "properties": { "int64": 1234567890123, "int64list": [ 1234567890123 ] }, "geometry": null }') < 0:
        gdaltest.post_reason('failure')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test workaround for 64bit values (returned as strings)


def ogr_geojson_27():

    if gdaltest.geojson_drv is None:
        return 'skip'

    gdal.PushErrorHandler('CPLQuietErrorHandler')
    # Warning 1: Integer values probably ranging out of 64bit integer range
    # have been found. Will be clamped to INT64_MIN/INT64_MAX
    ds = ogr.Open("""{"type": "FeatureCollection", "features":[
{"type": "Feature",
 "geometry": {"type":"Point","coordinates":[1,2]},
 "properties": { "intvalue" : 1 }},
{"type": "Feature",
 "geometry": {"type":"Point","coordinates":[3,4]},
 "properties": { "intvalue" : 12345678901231234567890123 }},
 ]}""")
    gdal.PopErrorHandler()
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayerByName('OGRGeoJSON')

    feature = lyr.GetNextFeature()
    if feature.GetField("intvalue") != 1:
        feature.DumpReadable()
        return 'fail'

    feature = lyr.GetNextFeature()
    if feature.GetField("intvalue") != 9223372036854775807:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI point file with z value


def ogr_geojson_28():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrizpoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    # validate layer doesn't check z, but put it in
    extent = (2, 2, 49, 49, 1, 1)

    rc = validate_layer(lyr, None, 1, ogr.wkbPoint, 4, extent)
    if not rc:
        return 'fail'

    ref = lyr.GetSpatialRef()
    gcs = int(ref.GetAuthorityCode('GEOGCS'))

    if not gcs == 4326:
        gdaltest.post_reason("Spatial reference was not valid")
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('POINT(2 49 1)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFID() != 1:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFieldAsInteger('fooInt') != 2:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFieldAsDouble('fooDouble') != 3.4:
        feature.DumpReadable()
        return 'fail'

    if feature.GetFieldAsString('fooString') != '56':
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI linestring file with z


def ogr_geojson_29():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrizlinestring.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    # validate layer doesn't check z, but put it in
    extent = (2, 3, 49, 50, 1, 2)

    rc = validate_layer(lyr, None, 1, ogr.wkbLineString, 0, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('LINESTRING (2 49 1,3 50 2)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI multipoint file with z


def ogr_geojson_30():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrizmultipoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    # validate layer doesn't check z, but put it in
    extent = (2, 3, 49, 50, 1, 2)

    rc = validate_layer(lyr, None, 1, ogr.wkbMultiPoint, 4, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTIPOINT (2 49 1,3 50 2)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI polygon file with z


def ogr_geojson_31():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrizpolygon.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    # validate layer doesn't check z, but put it in
    extent = (2, 3, 49, 50, 1, 4)

    rc = validate_layer(lyr, None, 1, ogr.wkbPolygon, 0, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('POLYGON ((2 49 1,2 50 2,3 50 3,3 49 4,2 49 1))')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI multipoint file with m, but no z (hasM=true, hasZ omitted)


def ogr_geojson_32():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrihasmnozmultipoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    extent = (2, 3, 49, 50)

    rc = validate_layer(lyr, None, 1, ogr.wkbMultiPoint, 4, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTIPOINT M ((2 49 1),(3 50 2))')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI multipoint file with hasZ=true, but only 2 components.


def ogr_geojson_33():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esriinvalidhaszmultipoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    extent = (2, 3, 49, 50)

    rc = validate_layer(lyr, None, 1, ogr.wkbMultiPoint, 4, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTIPOINT (2 49,3 50)')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test reading ESRI multipoint file with z and m


def ogr_geojson_34():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/esrizmmultipoint.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayer(0)

    extent = (2, 3, 49, 50)

    rc = validate_layer(lyr, None, 1, ogr.wkbMultiPoint, 4, extent)
    if not rc:
        return 'fail'

    feature = lyr.GetNextFeature()
    ref_geom = ogr.CreateGeometryFromWkt('MULTIPOINT ZM ((2 49 1 100),(3 50 2 100))')
    if ogrtest.check_feature_geometry(feature, ref_geom) != 0:
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test handling of huge coordinates (#5377)


def ogr_geojson_35():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = gdaltest.geojson_drv.CreateDataSource('/vsimem/ogr_geojson_35.json')
    lyr = ds.CreateLayer('foo')
    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(1)
    geom = ogr.Geometry(ogr.wkbPoint)
    geom.AddPoint_2D(-1.79769313486231571e+308, -1.79769313486231571e+308)
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    gdal.PushErrorHandler('CPLQuietErrorHandler')

    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(2)
    geom = ogr.Geometry(ogr.wkbPoint)
    geom.AddPoint(-1.7e308 * 2, 1.7e308 * 2, 1.7e308 * 2)  # evaluates to -inf, inf
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(3)
    geom = ogr.Geometry(ogr.wkbLineString)
    geom.AddPoint_2D(0, 0)
    geom.AddPoint_2D(-1.7e308 * 2, 1.7e308 * 2)  # evaluates to -inf, inf
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(4)
    geom = ogr.Geometry(ogr.wkbPolygon)
    geom2 = ogr.Geometry(ogr.wkbLinearRing)
    geom2.AddPoint_2D(0, 0)
    geom2.AddPoint_2D(-1.7e308 * 2, 1.7e308 * 2)  # evaluates to -inf, inf
    geom.AddGeometry(geom2)
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(5)
    geom = ogr.Geometry(ogr.wkbMultiPoint)
    geom2 = ogr.Geometry(ogr.wkbPoint)
    geom2.AddPoint_2D(0, 0)
    geom2 = ogr.Geometry(ogr.wkbPoint)
    geom2.AddPoint_2D(-1.7e308 * 2, 1.7e308 * 2)  # evaluates to -inf, inf
    geom.AddGeometry(geom2)
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(6)
    geom = ogr.Geometry(ogr.wkbMultiLineString)
    geom2 = ogr.Geometry(ogr.wkbLineString)
    geom2.AddPoint_2D(0, 0)
    geom2 = ogr.Geometry(ogr.wkbLineString)
    geom2.AddPoint_2D(-1.7e308 * 2, 1.7e308 * 2)  # evaluates to -inf, inf
    geom.AddGeometry(geom2)
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    feat = ogr.Feature(lyr.GetLayerDefn())
    feat.SetFID(7)
    geom = ogr.Geometry(ogr.wkbMultiPolygon)
    geom2 = ogr.Geometry(ogr.wkbPolygon)
    geom3 = ogr.Geometry(ogr.wkbLinearRing)
    geom3.AddPoint_2D(0, 0)
    geom2.AddGeometry(geom3)
    geom2 = ogr.Geometry(ogr.wkbPolygon)
    geom3 = ogr.Geometry(ogr.wkbLinearRing)
    geom3.AddPoint_2D(-1.7e308 * 2, 1.7e308 * 2)  # evaluates to -inf, inf
    geom2.AddGeometry(geom3)
    geom.AddGeometry(geom2)
    feat.SetGeometry(geom)
    lyr.CreateFeature(feat)

    gdal.PopErrorHandler()

    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_35.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_35.json')

    if data.find('-1.79') == -1 and data.find('e+308') == -1:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'
    for id in range(2, 8):
        if data.find('{ "type": "Feature", "id": %d, "properties": { }, "geometry": null }' % id) == -1:
            gdaltest.post_reason('fail')
            print(data)
            return 'fail'

    return 'success'

###############################################################################
# Test reading file with UTF-8 BOM (which is supposed to be illegal in JSON...) (#5630)


def ogr_geojson_36():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/point_with_utf8bom.json')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'
    ds = None

    return 'success'

#########################################################################
# Test boolean type support


def ogr_geojson_37():

    if gdaltest.geojson_drv is None:
        return 'skip'

    # Test read support
    ds = ogr.Open("""{"type": "FeatureCollection","features": [
{ "type": "Feature", "properties": { "bool" : false, "not_bool": false, "bool_list" : [false, true], "notbool_list" : [false, 3]}, "geometry": null  },
{ "type": "Feature", "properties": { "bool" : true, "not_bool": 2, "bool_list" : [true] }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('bool')).GetType() != ogr.OFTInteger or \
       feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('bool')).GetSubType() != ogr.OFSTBoolean:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('not_bool')).GetSubType() != ogr.OFSTNone:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('bool_list')).GetType() != ogr.OFTIntegerList or \
       feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('bool_list')).GetSubType() != ogr.OFSTBoolean:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('notbool_list')).GetSubType() != ogr.OFSTNone:
        gdaltest.post_reason('failure')
        return 'fail'
    f = lyr.GetNextFeature()
    if f.GetField('bool') != 0 or f.GetField('bool_list') != [0, 1]:
        gdaltest.post_reason('failure')
        f.DumpReadable()
        return 'fail'

    out_ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_37.json')
    out_lyr = out_ds.CreateLayer('test')
    for i in range(feat_defn.GetFieldCount()):
        out_lyr.CreateField(feat_defn.GetFieldDefn(i))
    out_f = ogr.Feature(out_lyr.GetLayerDefn())
    out_f.SetFrom(f)
    out_lyr.CreateFeature(out_f)
    out_ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_37.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_37.json')

    if data.find('"bool": false, "not_bool": 0, "bool_list": [ false, true ], "notbool_list": [ 0, 3 ]') < 0:
        gdaltest.post_reason('failure')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test datetime/date/time type support


def ogr_geojson_38():

    if gdaltest.geojson_drv is None:
        return 'skip'

    # Test read support
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "properties": { "dt": "2014-11-20 12:34:56+0100", "dt2": "2014\/11\/20", "date":"2014\/11\/20", "time":"12:34:56", "no_dt": "2014-11-20 12:34:56+0100", "no_dt2": "2014-11-20 12:34:56+0100" }, "geometry": null },
{ "type": "Feature", "properties": { "dt": "2014\/11\/20", "dt2": "2014\/11\/20T12:34:56Z", "date":"2014-11-20", "time":"12:34:56", "no_dt": "foo", "no_dt2": 1 }, "geometry": null }
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('dt')).GetType() != ogr.OFTDateTime:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('dt2')).GetType() != ogr.OFTDateTime:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('date')).GetType() != ogr.OFTDate:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('time')).GetType() != ogr.OFTTime:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('no_dt')).GetType() != ogr.OFTString:
        gdaltest.post_reason('failure')
        return 'fail'
    if feat_defn.GetFieldDefn(feat_defn.GetFieldIndex('no_dt2')).GetType() != ogr.OFTString:
        gdaltest.post_reason('failure')
        return 'fail'
    f = lyr.GetNextFeature()
    if f.GetField('dt') != '2014/11/20 12:34:56+01' or f.GetField('dt2') != '2014/11/20 00:00:00' or \
       f.GetField('date') != '2014/11/20' or f.GetField('time') != '12:34:56':
        gdaltest.post_reason('failure')
        f.DumpReadable()
        return 'fail'
    f = lyr.GetNextFeature()
    if f.GetField('dt') != '2014/11/20 00:00:00' or f.GetField('dt2') != '2014/11/20 12:34:56+00' or \
       f.GetField('date') != '2014/11/20' or f.GetField('time') != '12:34:56':
        gdaltest.post_reason('failure')
        f.DumpReadable()
        return 'fail'

    return 'success'

###############################################################################
# Test id top-object level


def ogr_geojson_39():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : "foo", "properties": { "bar" : "baz" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTString:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != 'foo' or feat.GetField('bar') != 'baz':
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # Crazy case: properties.id has the precedence because we arbitrarily decided that...
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : "foo", "properties": { "id" : 6 }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != 6:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # Same with 2 features
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : "foo", "properties": { "id" : 6 }, "geometry": null },
{ "type": "Feature", "id" : "bar", "properties": { "id" : 7 }, "geometry": null }
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != 6:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # Crazy case: properties.id has the precedence because we arbitrarily decided that...
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : "foo", "properties": { "id" : "baz" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTString:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != 'baz':
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # id and properties.ID (#6538)
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : 1, "properties": { "ID": 2 }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'ID' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetFID() != 1 or feat.GetField('ID') != 2:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # Test handling of duplicated id
    gdal.ErrorReset()
    with gdaltest.error_handler():
        ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : 1, "properties": { "foo": "bar" }, "geometry": null },
{ "type": "Feature", "id" : 1, "properties": { "foo": "baz" }, "geometry": null },
{ "type": "Feature", "id" : 2, "properties": { "foo": "baw" }, "geometry": null }
] }""")
    if gdal.GetLastErrorMsg() == '':
        gdaltest.post_reason('expected warning')
        return 'fail'
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    feat = lyr.GetNextFeature()
    if feat.GetFID() != 1 or feat.GetField('foo') != 'bar':
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetFID() != 2 or feat.GetField('foo') != 'baz':
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetFID() != 3 or feat.GetField('foo') != 'baw':
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # negative id
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : -1, "properties": { "foo": "bar" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != -1:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # negative id 64bit
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : -1234567890123, "properties": { "foo": "bar" }, "geometry": null },
{ "type": "Feature", "id" : -2, "properties": { "foo": "baz" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTInteger64:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != -1234567890123:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # negative id
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : -2, "properties": { "foo": "baz" }, "geometry": null },
{ "type": "Feature", "id" : -1234567890123, "properties": { "foo": "bar" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTInteger64:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != -2:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # positive and then negative id
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : 1, "properties": { "foo": "baz" }, "geometry": null },
{ "type": "Feature", "id" : -1, "properties": { "foo": "bar" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(1).GetName() != 'id' or feat_defn.GetFieldDefn(1).GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != 1:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    # mix of int and string id
    ds = ogr.Open("""{"type": "FeatureCollection", "features": [
{ "type": "Feature", "id" : -2, "properties": { "foo": "baz" }, "geometry": null },
{ "type": "Feature", "id" : "str", "properties": { "foo": "bar" }, "geometry": null },
{ "type": "Feature", "id" : -3, "properties": { "foo": "baz" }, "geometry": null },
] }""")
    lyr = ds.GetLayer(0)
    feat_defn = lyr.GetLayerDefn()
    if feat_defn.GetFieldDefn(0).GetName() != 'id' or feat_defn.GetFieldDefn(0).GetType() != ogr.OFTString:
        gdaltest.post_reason('fail')
        return 'fail'
    feat = lyr.GetNextFeature()
    if feat.GetField('id') != '-2':
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    return 'success'

###############################################################################
# Test nested attributes


def ogr_geojson_40():

    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = gdal.OpenEx("""{
  "type": "FeatureCollection",
  "features" :
  [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [ 2, 49 ]
      },
      "properties": {
        "a_property": 1,
        "some_object": {
          "a_property": 1,
          "another_property": 2
        }
      }
    },
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [ 2, 49 ]
      },
      "properties": {
        "a_property": "foo",
        "some_object": {
          "a_property": 1,
          "another_property": 2.34
        }
      }
    }
  ]
}""", gdal.OF_VECTOR, open_options=['FLATTEN_NESTED_ATTRIBUTES=YES', 'NESTED_ATTRIBUTE_SEPARATOR=.'])
    lyr = ds.GetLayer(0)
    feat = lyr.GetNextFeature()
    feat = lyr.GetNextFeature()
    if feat.GetField('a_property') != 'foo' or feat.GetField('some_object.a_property') != 1 or \
       feat.GetField('some_object.another_property') != 2.34:
        gdaltest.post_reason('fail')
        feat.DumpReadable()
        return 'fail'

    return 'success'

###############################################################################
# Test ogr.CreateGeometryFromJson()


def ogr_geojson_41():

    if gdaltest.geojson_drv is None:
        return 'skip'

    # Check that by default we return a WGS 84 SRS
    g = ogr.CreateGeometryFromJson("{ 'type': 'Point', 'coordinates' : [ 2, 49] }")
    if g.ExportToWkt() != 'POINT (2 49)':
        gdaltest.post_reason('fail')
        return 'fail'
    srs = g.GetSpatialReference()
    g = None

    if srs.ExportToWkt().find('WGS 84') < 0:
        gdaltest.post_reason('fail')
        return 'fail'

    # But if a crs object is set (allowed originally, but not recommended!), we use it
    g = ogr.CreateGeometryFromJson('{ "type": "Point", "coordinates" : [ 2, 49], "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::4322" } } }')
    srs = g.GetSpatialReference()
    if srs.ExportToWkt().find('4322') < 0:
        gdaltest.post_reason('fail')
        return 'fail'

    # But if a crs object is set to null, set no crs
    g = ogr.CreateGeometryFromJson('{ "type": "Point", "coordinates" : [ 2, 49], "crs": null }')
    srs = g.GetSpatialReference()
    if srs:
        gdaltest.post_reason('fail')
        return 'fail'

    return 'success'

###############################################################################
# Test ESRI FeatureService scrolling


def ogr_geojson_42():

    if gdaltest.geojson_drv is None:
        return 'skip'

    gdal.SetConfigOption('CPL_CURL_ENABLE_VSIMEM', 'YES')

    resultOffset0 = """
{ "type":"FeatureCollection",
  "properties" : {
    "exceededTransferLimit" : true
  },
  "features" :
  [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [ 2, 49 ]
      },
      "properties": {
        "id": 1,
        "a_property": 1,
      }
    } ] }"""

    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1', resultOffset0)
    ds = ogr.Open('/vsimem/geojson/test.json?resultRecordCount=1')
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    f = lyr.GetNextFeature()
    if f is not None:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None
    gdal.Unlink('/vsimem/geojson/test.json?resultRecordCount=1')

    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=10', resultOffset0)
    gdal.PushErrorHandler()
    ds = ogr.Open('/vsimem/geojson/test.json?resultRecordCount=10')
    gdal.PopErrorHandler()
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    f = lyr.GetNextFeature()
    if f is not None:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None
    gdal.Unlink('/vsimem/geojson/test.json?resultRecordCount=10')

    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?', resultOffset0)
    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1&resultOffset=0', resultOffset0)

    ds = ogr.Open('/vsimem/geojson/test.json?')
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    f = lyr.GetNextFeature()
    if f is not None:
        gdaltest.post_reason('fail')
        return 'fail'
    lyr.ResetReading()
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 1:
        gdaltest.post_reason('fail')
        return 'fail'

    resultOffset1 = """
{ "type":"FeatureCollection",
  "features" :
  [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [ 2, 49 ]
      },
      "properties": {
        "id": 2,
        "a_property": 1,
      }
    } ] }"""
    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1&resultOffset=1', resultOffset1)
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 2:
        gdaltest.post_reason('fail')
        return 'fail'
    f = lyr.GetNextFeature()
    if f is not None:
        gdaltest.post_reason('fail')
        return 'fail'

    gdal.PushErrorHandler()
    fc = lyr.GetFeatureCount()
    gdal.PopErrorHandler()
    if fc != 2:
        gdaltest.post_reason('fail')
        return 'fail'

    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1&returnCountOnly=true',
                           """{ "count": 123456}""")
    fc = lyr.GetFeatureCount()
    if fc != 123456:
        gdaltest.post_reason('fail')
        return 'fail'

    gdal.PushErrorHandler()
    extent = lyr.GetExtent()
    gdal.PopErrorHandler()
    if extent != (2, 2, 49, 49):
        gdaltest.post_reason('fail')
        print(extent)
        return 'fail'

    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1&returnExtentOnly=true&f=geojson',
                           """{"type":"FeatureCollection","bbox":[1, 2, 3, 4],"features":[]}""")
    extent = lyr.GetExtent()
    if extent != (1.0, 3.0, 2.0, 4.0):
        gdaltest.post_reason('fail')
        print(extent)
        return 'fail'

    if lyr.TestCapability(ogr.OLCFastFeatureCount) != 1:
        gdaltest.post_reason('fail')
        return 'fail'

    if lyr.TestCapability(ogr.OLCFastGetExtent) != 0:
        gdaltest.post_reason('fail')
        return 'fail'

    if lyr.TestCapability('foo') != 0:
        gdaltest.post_reason('fail')
        return 'fail'

    # Test scrolling with ESRI json
    resultOffset0 = """
{
  "objectIdFieldName" : "objectid",
  "geometryType" : "esriGeometryPoint",
  "fields" : [
    {
      "name" : "objectid",
      "alias" : "Object ID",
      "type" : "esriFieldTypeOID"
    },

  ],
  "features" : [
    {
      "geometry" : {
        "x" : 2,
        "y" : 49,
        "z" : 1
      },
      "attributes" : {
        "objectid" : 1
      }
    }
  ],
  "exceededTransferLimit": true
}
"""

    resultOffset1 = """
{
  "objectIdFieldName" : "objectid",
  "geometryType" : "esriGeometryPoint",
  "fields" : [
    {
      "name" : "objectid",
      "alias" : "Object ID",
      "type" : "esriFieldTypeOID"
    },

  ],
  "features" : [
    {
      "geometry": null,
      "attributes" : {
        "objectid" : 20
      }
    }
  ]
}
"""

    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1', resultOffset0)
    gdal.FileFromMemBuffer('/vsimem/geojson/test.json?resultRecordCount=1&resultOffset=1', resultOffset1)
    ds = ogr.Open('/vsimem/geojson/test.json?resultRecordCount=1')
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    f = lyr.GetNextFeature()
    if f is None or f.GetFID() != 20:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None
    gdal.Unlink('/vsimem/geojson/test.json?resultRecordCount=1')
    gdal.Unlink('/vsimem/geojson/test.json?resultRecordCount=1&resultOffset=1')

    return 'success'

###############################################################################
# Test Feature without geometry


def ogr_geojson_43():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open("""{"type": "FeatureCollection", "features":[
{"type": "Feature", "properties": {"foo": "bar"}}]}""")
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayerByName('OGRGeoJSON')

    feature = lyr.GetNextFeature()
    if feature.GetFieldAsString("foo") != 'bar':
        feature.DumpReadable()
        return 'fail'

    lyr = None
    ds = None

    return 'success'

###############################################################################
# Test null Feature (#6166)


def ogr_geojson_44():
    if gdaltest.geojson_drv is None:
        return 'skip'

    with gdaltest.error_handler():
        ogr.Open("""{"type": "FeatureCollection", "features":[ null ]}""")

    return 'success'

###############################################################################
# Test native data support


def ogr_geojson_45():
    if gdaltest.geojson_drv is None:
        return 'skip'

    # Test read support
    content = """{"type": "FeatureCollection", "foo": "bar", "bar": "baz",
    "features":[ { "type": "Feature", "foo": ["bar", "baz", 1.0, true, false,[],{}], "properties": { "myprop": "myvalue" }, "geometry": null } ]}"""
    for i in range(2):
        if i == 0:
            ds = gdal.OpenEx(content, gdal.OF_VECTOR, open_options=['NATIVE_DATA=YES'])
        else:
            gdal.FileFromMemBuffer('/vsimem/ogr_geojson_45.json', content)
            ds = gdal.OpenEx('/vsimem/ogr_geojson_45.json', gdal.OF_VECTOR, open_options=['NATIVE_DATA=YES'])
        lyr = ds.GetLayer(0)
        native_data = lyr.GetMetadataItem("NATIVE_DATA", "NATIVE_DATA")
        if native_data != '{ "foo": "bar", "bar": "baz" }':
            gdaltest.post_reason('fail')
            print(native_data)
            return 'fail'
        native_media_type = lyr.GetMetadataItem("NATIVE_MEDIA_TYPE", "NATIVE_DATA")
        if native_media_type != 'application/vnd.geo+json':
            gdaltest.post_reason('fail')
            print(native_media_type)
            return 'fail'
        f = lyr.GetNextFeature()
        native_data = f.GetNativeData()
        if i == 0:
            expected = [
                '{ "type": "Feature", "foo": [ "bar", "baz", 1.000000, true, false, [ ], { } ], "properties": { "myprop": "myvalue" }, "geometry": null }',
                '{ "type": "Feature", "foo": [ "bar", "baz", 1.0, true, false, [ ], { } ], "properties": { "myprop": "myvalue" }, "geometry": null }']
        else:
            expected = ['{"type":"Feature","foo":["bar","baz",1.0,true,false,[],{}],"properties":{"myprop":"myvalue"},"geometry":null}']
        if native_data not in expected:
            gdaltest.post_reason('fail')
            print(native_data)
            return 'fail'
        native_media_type = f.GetNativeMediaType()
        if native_media_type != 'application/vnd.geo+json':
            gdaltest.post_reason('fail')
            print(native_media_type)
            return 'fail'
        ds = None
        if i == 1:
            gdal.Unlink('/vsimem/ogr_geojson_45.json')

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_45.json')
    lyr = ds.CreateLayer('test', options=[
        'NATIVE_DATA={ "type": "ignored", "bbox": [ 0, 0, 0, 0 ], "foo": "bar", "bar": "baz", "features": "ignored" }',
        'NATIVE_MEDIA_TYPE=application/vnd.geo+json'])
    f = ogr.Feature(lyr.GetLayerDefn())
    json_geom = """{ "type": "GeometryCollection", "foo_gc": "bar_gc", "geometries" : [
                        { "type": "Point", "foo_point": "bar_point", "coordinates": [0,1,2, 3] },
                        { "type": "LineString", "foo_linestring": "bar_linestring", "coordinates": [[0,1,2, 4]] },
                        { "type": "MultiPoint", "foo_multipoint": "bar_multipoint", "coordinates": [[0,1,2, 5]] },
                        { "type": "MultiLineString", "foo_multilinestring": "bar_multilinestring", "coordinates": [[[0,1,2, 6]]] },
                        { "type": "Polygon", "foo_polygon": "bar_polygon", "coordinates": [[[0,1,2, 7]]] },
                        { "type": "MultiPolygon", "foo_multipolygon": "bar_multipolygon", "coordinates": [[[[0,1,2, 8]]]] }
                        ] }"""
    f.SetNativeData('{ "type": "ignored", "bbox": "ignored", "properties" : "ignored", "foo_feature": "bar_feature", "geometry": %s }' % json_geom)
    f.SetNativeMediaType('application/vnd.geo+json')
    f.SetGeometry(ogr.CreateGeometryFromJson(json_geom))
    lyr.CreateFeature(f)
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_45.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_45.json')

    if data.find('"bbox": [ 0, 1, 2, 0, 1, 2 ],') < 0 or \
       data.find('"foo": "bar"') < 0 or data.find('"bar": "baz"') < 0 or \
       data.find('"foo_feature": "bar_feature"') < 0 or \
       data.find('"foo_gc": "bar_gc"') < 0 or \
       data.find('"foo_point": "bar_point"') < 0 or data.find('3') < 0 or \
       data.find('"foo_linestring": "bar_linestring"') < 0 or data.find('4') < 0 or \
       data.find('"foo_multipoint": "bar_multipoint"') < 0 or data.find('5') < 0 or \
       data.find('"foo_multilinestring": "bar_multilinestring"') < 0 or data.find('6') < 0 or \
       data.find('"foo_polygon": "bar_polygon"') < 0 or data.find('7') < 0 or \
       data.find('"foo_multipolygon": "bar_multipolygon"') < 0 or data.find('8') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    # Test native support with string id
    src_ds = gdal.OpenEx("""{
"type": "FeatureCollection",
"features": [
{ "type": "Feature",
  "id": "foobarbaz",
  "properties": {},
  "geometry": null
}
]
}
""", open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON')

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": "foobarbaz", "properties": { }, "geometry": null }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Test native support with numeric id
    src_ds = gdal.OpenEx("""{
"type": "FeatureCollection",
"features": [
{ "type": "Feature",
  "id": 1234657890123,
  "properties": {},
  "geometry": null
}
]
}
""", open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON')

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "id": 1234657890123, "properties": { }, "geometry": null }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    return 'success'

###############################################################################
# Test that writing JSon content as value of a string field is serialized as it


def ogr_geojson_46():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_46.json')
    lyr = ds.CreateLayer('test')
    lyr.CreateField(ogr.FieldDefn('myprop'))
    f = ogr.Feature(lyr.GetLayerDefn())
    f['myprop'] = '{ "a": "b" }'
    lyr.CreateFeature(f)
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_46.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_46.json')

    if data.find('{ "myprop": { "a": "b" } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test update support


def ogr_geojson_47():
    if gdaltest.geojson_drv is None:
        return 'skip'

    # ERROR 6: Update from inline definition not supported
    with gdaltest.error_handler():
        ds = ogr.Open('{"type": "FeatureCollection", "features":[]}', update=1)
    if ds is not None:
        gdaltest.post_reason('fail')
        return 'fail'

    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_47.json',
                           """{"type": "FeatureCollection", "foo": "bar",
    "features":[ { "type": "Feature", "bar": "baz", "properties": { "myprop": "myvalue" }, "geometry": null } ]}""")

    # Test read support
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    f.SetField("myprop", "another_value")
    lyr.SetFeature(f)
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_47.json', 'rb')
    if fp is not None:
        data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
        gdal.VSIFCloseL(fp)
    else:
        data = None

    # we don't want crs if there's no in the source
    if data.find('"foo": "bar"') < 0 or data.find('"bar": "baz"') < 0 or \
       data.find('crs') >= 0 or \
       data.find('"myprop": "another_value"') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    # Test append support
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(1 2)'))
    lyr.CreateFeature(f)
    if f.GetFID() != 1:
        gdaltest.post_reason('fail')
        f.DumpReadable()
        return 'fail'
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(2 3)'))
    lyr.CreateFeature(f)
    f = lyr.GetNextFeature()
    if f.GetFID() != 0:
        gdaltest.post_reason('fail')
        f.DumpReadable()
        return 'fail'
    ds = None

    # Test append support
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(4 5)'))
    lyr.CreateFeature(f)
    f.SetField("myprop", "value_of_point_4_5")
    lyr.SetFeature(f)
    ds = None

    ds = ogr.Open('/vsimem/ogr_geojson_47.json')
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 4:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_47.json', 'rb')
    if fp is not None:
        data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
        gdal.VSIFCloseL(fp)
    else:
        data = None

    # we don't want crs if there's no in the source
    if data.find('"foo": "bar"') < 0 or data.find('"bar": "baz"') < 0 or \
       data.find('crs') >= 0 or \
       data.find('"myprop": "another_value"') < 0 or \
       data.find('"myprop": "value_of_point_4_5"') < 0 or \
       data.find('id') >= 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    gdal.Unlink('/vsimem/ogr_geojson_47.json')

    # Test appending to empty features array
    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_47.json', """{ "type": "FeatureCollection", "features": []}""")
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    lyr.CreateFeature(f)
    ds = None
    ds = ogr.Open('/vsimem/ogr_geojson_47.json')
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None

    # Test appending to array ending with non feature
    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_47.json', """{ "type": "FeatureCollection", "features": [ null ]}""")
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    lyr.CreateFeature(f)
    ds = None
    ds = ogr.Open('/vsimem/ogr_geojson_47.json')
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None

    # Test appending to feature collection not ending with "features"
    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_47.json', """{ "type": "FeatureCollection", "features": [], "something": "else"}""")
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    lyr.CreateFeature(f)
    ds = None
    ds = ogr.Open('/vsimem/ogr_geojson_47.json')
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_47.json', 'rb')
    if fp is not None:
        data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
        gdal.VSIFCloseL(fp)
    else:
        data = None

    if data.find('something') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    # Test appending to feature collection with "bbox"
    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_47.json', """{ "type": "FeatureCollection", "bbox": [0,0,0,0], "features": [ { "type": "Feature", "geometry": { "type": "Point", "coordinates": [0,0]} } ]}""")
    ds = ogr.Open('/vsimem/ogr_geojson_47.json', update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    lyr.CreateFeature(f)
    ds = None
    ds = ogr.Open('/vsimem/ogr_geojson_47.json')
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 2:
        gdaltest.post_reason('fail')
        return 'fail'
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_47.json', 'rb')
    if fp is not None:
        data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
        gdal.VSIFCloseL(fp)
    else:
        data = None

    if data.find('bbox') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    gdal.Unlink('/vsimem/ogr_geojson_47.json')

    return 'success'

###############################################################################
# Test update support with file that has a single feature not in a FeatureCollection


def ogr_geojson_48():
    if gdaltest.geojson_drv is None:
        return 'skip'

    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_48.json',
                           """{ "type": "Feature", "bar": "baz", "bbox": [2,49,2,49], "properties": { "myprop": "myvalue" }, "geometry": {"type": "Point", "coordinates": [ 2, 49]} }""")

    # Test read support
    ds = ogr.Open('/vsimem/ogr_geojson_48.json', update=1)
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    f.SetField("myprop", "another_value")
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT (3 50)'))
    lyr.SetFeature(f)
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_48.json', 'rb')
    if fp is not None:
        data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
        gdal.VSIFCloseL(fp)
    else:
        data = None

    gdal.Unlink('/vsimem/ogr_geojson_48.json')

    # we don't want crs if there's no in the source
    if data.find('"bar": "baz"') < 0 or \
       data.find('"bbox": [ 3.0, 50.0, 3.0, 50.0 ]') < 0 or \
       data.find('crs') >= 0 or \
       data.find('FeatureCollection') >= 0 or \
       data.find('"myprop": "another_value"') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test ARRAY_AS_STRING


def ogr_geojson_49():
    if gdaltest.geojson_drv is None:
        return 'skip'

    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_49.json',
                           """{ "type": "Feature", "properties": { "foo": ["bar"] }, "geometry": null }""")

    # Test read support
    ds = gdal.OpenEx('/vsimem/ogr_geojson_49.json', open_options=['ARRAY_AS_STRING=YES'])
    lyr = ds.GetLayer(0)
    if lyr.GetLayerDefn().GetFieldDefn(0).GetType() != ogr.OFTString:
        gdaltest.post_reason('fail')
        return 'fail'
    f = lyr.GetNextFeature()
    if f['foo'] != '[ "bar" ]':
        gdaltest.post_reason('fail')
        f.DumpReadable()
        return 'fail'
    ds = None

    gdal.Unlink('/vsimem/ogr_geojson_49.json')

    return 'success'

###############################################################################
# Test that we serialize floating point values with enough significant figures


def ogr_geojson_50():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_50.json')
    lyr = ds.CreateLayer('test')
    lyr.CreateField(ogr.FieldDefn('val', ogr.OFTReal))
    f = ogr.Feature(lyr.GetLayerDefn())
    f['val'] = 1.23456789012456
    lyr.CreateFeature(f)
    # To test smart rounding
    f = ogr.Feature(lyr.GetLayerDefn())
    f['val'] = 5268.813
    lyr.CreateFeature(f)
    f = None
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_50.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_50.json')

    if data.find('1.23456789012456') < 0 and data.find('5268.813 ') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    # If SIGNIFICANT_FIGURES is explicitly specified, and COORDINATE_PRECISION not,
    # then it also applies to coordinates
    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_50.json')
    lyr = ds.CreateLayer('test', options=['SIGNIFICANT_FIGURES=17'])
    lyr.CreateField(ogr.FieldDefn('val', ogr.OFTReal))
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT (0.0000123456789012456 0)'))
    lyr.CreateFeature(f)
    f = None
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_50.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_50.json')

    if data.find('1.23456789012456') < 0 and data.find('-5') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    # If SIGNIFICANT_FIGURES is explicitly specified, and COORDINATE_PRECISION too,
    # then SIGNIFICANT_FIGURES only applies to non-coordinates floating point values.
    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_50.json')
    lyr = ds.CreateLayer('test', options=['COORDINATE_PRECISION=15', 'SIGNIFICANT_FIGURES=17'])
    lyr.CreateField(ogr.FieldDefn('val', ogr.OFTReal))
    f = ogr.Feature(lyr.GetLayerDefn())
    f['val'] = 1.23456789012456
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT (0.0000123456789012456 0)'))
    lyr.CreateFeature(f)
    f = None
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_50.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_50.json')

    if data.find('0.00001234') < 0 or data.find('1.23456789012456') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test writing empty geometries


def ogr_geojson_51():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_51.json')
    lyr = ds.CreateLayer('test')
    lyr.CreateField(ogr.FieldDefn('id', ogr.OFTInteger))
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 1
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 2
    f.SetGeometry(ogr.CreateGeometryFromWkt('LINESTRING EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 3
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 4
    f.SetGeometry(ogr.CreateGeometryFromWkt('MULTIPOINT EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 5
    f.SetGeometry(ogr.CreateGeometryFromWkt('MULTILINESTRING EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 6
    f.SetGeometry(ogr.CreateGeometryFromWkt('MULTIPOLYGON EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['id'] = 7
    f.SetGeometry(ogr.CreateGeometryFromWkt('GEOMETRYCOLLECTION EMPTY'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_51.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_51.json')

    if data.find('{ "id": 1 }, "geometry": null') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    if data.find('{ "id": 2 }, "geometry": { "type": "LineString", "coordinates": [ ] } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    if data.find('{ "id": 3 }, "geometry": { "type": "Polygon", "coordinates": [ ] } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    if data.find('{ "id": 4 }, "geometry": { "type": "MultiPoint", "coordinates": [ ] } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    if data.find('{ "id": 5 }, "geometry": { "type": "MultiLineString", "coordinates": [ ] } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    if data.find('{ "id": 6 }, "geometry": { "type": "MultiPolygon", "coordinates": [ ] } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    if data.find('{ "id": 7 }, "geometry": { "type": "GeometryCollection", "geometries": [ ] } }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test NULL type detection


def ogr_geojson_52():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('data/nullvalues.geojson')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    if ds.GetLayerCount() is not 1:
        gdaltest.post_reason('Wrong number of layers')
        return 'fail'

    lyr = ds.GetLayerByName('nullvalues')
    if lyr is None:
        gdaltest.post_reason('Missing layer called nullvalues')
        return 'fail'

    fld = lyr.GetLayerDefn().GetFieldDefn(0)
    if fld.GetNameRef() != 'int':
        return 'fail'
    if fld.GetType() != ogr.OFTInteger:
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(1)
    if fld.GetNameRef() != 'string':
        return 'fail'
    if fld.GetType() != ogr.OFTString:
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(2)
    if fld.GetNameRef() != 'double':
        return 'fail'
    if fld.GetType() != ogr.OFTReal:
        return 'fail'

    return 'success'

###############################################################################
# Test that M is ignored (this is a test of OGRLayer::CreateFeature() actually)


def ogr_geojson_53():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_53.json')
    lyr = ds.CreateLayer('test')
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POINT ZM (1 2 3 4)'))
    lyr.CreateFeature(f)
    ds = None

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_53.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_53.json')

    if data.find('{ "type": "Point", "coordinates": [ 1.0, 2.0, 3.0 ] }') < 0:
        gdaltest.post_reason('fail')
        print(data)
        return 'fail'

    return 'success'

###############################################################################
# Test NULL type detection when first value is null


def ogr_geojson_54():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open("""{
   "type": "FeatureCollection",

  "features": [
      { "type": "Feature", "properties": { "int": null, "string": null, "double": null, "dt" : null, "boolean": null, "null": null }, "geometry": null },
      { "type": "Feature", "properties": { "int": 168, "string": "string", "double": 1.23, "dt" : "2016-05-18T12:34:56Z", "boolean": true }, "geometry": null }
  ]
}
""")
    lyr = ds.GetLayer(0)

    fld = lyr.GetLayerDefn().GetFieldDefn(0)
    if fld.GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(1)
    if fld.GetType() != ogr.OFTString:
        gdaltest.post_reason('fail')
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(2)
    if fld.GetType() != ogr.OFTReal:
        gdaltest.post_reason('fail')
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(3)
    if fld.GetType() != ogr.OFTDateTime:
        gdaltest.post_reason('fail')
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(4)
    if fld.GetType() != ogr.OFTInteger:
        gdaltest.post_reason('fail')
        return 'fail'
    if fld.GetSubType() != ogr.OFSTBoolean:
        gdaltest.post_reason('fail')
        return 'fail'
    if fld.GetWidth() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    fld = lyr.GetLayerDefn().GetFieldDefn(5)
    if fld.GetType() != ogr.OFTString:
        gdaltest.post_reason('fail')
        return 'fail'
    return 'success'

###############################################################################
# Test RFC 7946


def read_file(filename):
    f = gdal.VSIFOpenL(filename, "rb")
    if f is None:
        return None
    content = gdal.VSIFReadL(1, 10000, f).decode('UTF-8')
    gdal.VSIFCloseL(f)
    return content


def ogr_geojson_55():
    if gdaltest.geojson_drv is None:
        return 'skip'

    # Basic test for standard bbox and coordinate truncation
    gdal.VectorTranslate('/vsimem/out.json', """{
   "type": "FeatureCollection",
  "features": [
      { "type": "Feature", "id": 123, "properties": {}, "geometry": { "type": "Point", "coordinates": [2.123456789, 49] } },
      { "type": "Feature", "id": 124, "properties": {}, "geometry": { "type": "Point", "coordinates": [3, 50] } }
  ]
}""", options='-f GeoJSON -lco RFC7946=YES -lco WRITE_BBOX=YES -preserve_fid')

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 2.1234568, 49.0000000, 3.0000000, 50.0000000 ],
"features": [
{ "type": "Feature", "id": 123, "properties": { }, "bbox": [ 2.1234568, 49.0, 2.1234568, 49.0 ], "geometry": { "type": "Point", "coordinates": [ 2.1234568, 49.0 ] } },
{ "type": "Feature", "id": 124, "properties": { }, "bbox": [ 3.0, 50.0, 3.0, 50.0 ], "geometry": { "type": "Point", "coordinates": [ 3.0, 50.0 ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Test polygon winding order
    gdal.VectorTranslate('/vsimem/out.json', """{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[2,49],[3,49],[3,50],[2,50],[2,49]],[[2.1,49.1],[2.1,49.9],[2.9,49.9],[2.9,49.1],[2.1,49.1]]] } },
{ "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[2,49],[2,50],[3,50],[3,49],[2,49]],[[2.1,49.1],[2.9,49.1],[2.9,49.9],[2.1,49.9],[2.1,49.1]]] } },
]
}
""", format='GeoJSON', layerCreationOptions=['RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 2.0000000, 49.0000000, 3.0000000, 50.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 2.0, 49.0, 3.0, 50.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 2.0, 49.0 ], [ 3.0, 49.0 ], [ 3.0, 50.0 ], [ 2.0, 50.0 ], [ 2.0, 49.0 ] ], [ [ 2.1, 49.1 ], [ 2.1, 49.9 ], [ 2.9, 49.9 ], [ 2.9, 49.1 ], [ 2.1, 49.1 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 2.0, 49.0, 3.0, 50.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 2.0, 49.0 ], [ 3.0, 49.0 ], [ 3.0, 50.0 ], [ 2.0, 50.0 ], [ 2.0, 49.0 ] ], [ [ 2.1, 49.1 ], [ 2.1, 49.9 ], [ 2.9, 49.9 ], [ 2.9, 49.1 ], [ 2.1, 49.1 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Test foreign member
    src_ds = gdal.OpenEx("""{
"type": "FeatureCollection",
"coordinates": "should not be found in output",
"geometries": "should not be found in output",
"geometry": "should not be found in output",
"properties": "should not be found in output",
"valid": "should be in output",
"crs": "should not be found in output",
"bbox": [0,0,0,0],
"features": [
{ "type": "Feature",
  "id": ["not expected as child of features"],
  "coordinates": "should not be found in output",
  "geometries": "should not be found in output",
  "features": "should not be found in output",
  "valid": "should be in output",
  "properties": { "foo": "bar" },
  "geometry": {
    "type": "Point",
    "bbox": [0,0,0,0],
    "geometry": "should not be found in output",
    "properties": "should not be found in output",
    "features": "should not be found in output",
    "valid": "should be in output",
    "coordinates": [2,49]
  }
}
]
}
""", open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['RFC7946=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"valid": "should be in output",
"bbox": [ 2.0000000, 49.0000000, 2.0000000, 49.0000000 ],
"features": [
{ "type": "Feature",
  "valid": "should be in output",
  "properties": { "id": [ "not expected as child of features" ], "foo": "bar" },
  "geometry": { "type": "Point", "coordinates": [ 2.0, 49.0 ], "valid": "should be in output" } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    return 'success'

###############################################################################
# Test RFC 7946 (that require geos)


def ogr_geojson_56():
    if gdaltest.geojson_drv is None:
        return 'skip'
    if not ogrtest.have_geos():
        return 'skip'

    # Test offsetting longitudes beyond antimeridian
    gdal.VectorTranslate('/vsimem/out.json', """{
  "type": "FeatureCollection",
  "features": [
      { "type": "Feature", "geometry": { "type": "Point", "coordinates": [182, 49] } },
      { "type": "Feature", "geometry": { "type": "Point", "coordinates": [-183, 50] } },
      { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[-183, 51],[-182, 48]] } },
      { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[182, 52],[183, 47]] } },
      { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[-183, 51],[-183, 48],[-182, 48],[-183, 48],[-183, 51]]] } },
      { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[183, 51],[183, 48],[182, 48],[183, 48],[183, 51]]] } },
  ]
}""", format='GeoJSON', layerCreationOptions=['RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ -178.0000000, 47.0000000, 178.0000000, 52.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ -178.0, 49.0, -178.0, 49.0 ], "geometry": { "type": "Point", "coordinates": [ -178.0, 49.0 ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 177.0, 50.0, 177.0, 50.0 ], "geometry": { "type": "Point", "coordinates": [ 177.0, 50.0 ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 177.0, 48.0, 178.0, 51.0 ], "geometry": { "type": "LineString", "coordinates": [ [ 177.0, 51.0 ], [ 178.0, 48.0 ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ -178.0, 47.0, -177.0, 52.0 ], "geometry": { "type": "LineString", "coordinates": [ [ -178.0, 52.0 ], [ -177.0, 47.0 ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 177.0, 48.0, 178.0, 51.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 177.0, 51.0 ], [ 177.0, 48.0 ], [ 178.0, 48.0 ], [ 177.0, 48.0 ], [ 177.0, 51.0 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ -178.0, 48.0, -177.0, 51.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -177.0, 51.0 ], [ -177.0, 48.0 ], [ -178.0, 48.0 ], [ -177.0, 48.0 ], [ -177.0, 51.0 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Test geometries across the antimeridian
    gdal.VectorTranslate('/vsimem/out.json', """{
  "type": "FeatureCollection",
  "features": [
      { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[179, 51],[-179, 48]] } },
      { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [[-179, 52],[179, 47]] } },
      { "type": "Feature", "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 179.0, 51.0 ], [ 180.0, 49.5 ] ], [ [ -180.0, 49.5 ], [ -179.0, 48.0 ] ] ] } },
      { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [[[177, 51],[-175, 51],[-175, 48],[177, 48],[177, 51]]] } },
      { "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 177.0, 51.0 ], [ 177.0, 48.0 ], [ 180.0, 48.0 ], [ 180.0, 51.0 ], [ 177.0, 51.0 ] ] ], [ [ [ -180.0, 51.0 ], [ -180.0, 48.0 ], [ -175.0, 48.0 ], [ -175.0, 51.0 ], [ -180.0, 51.0 ] ] ] ] } }
  ]
}""", format='GeoJSON', layerCreationOptions=['RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 177.0000000, 47.0000000, -175.0000000, 52.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 179.0, 48.0, -179.0, 51.0 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 179.0, 51.0 ], [ 180.0, 49.5 ] ], [ [ -180.0, 49.5 ], [ -179.0, 48.0 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 179.0, 47.0, -179.0, 52.0 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ -179.0, 52.0 ], [ -180.0, 49.5 ] ], [ [ 180.0, 49.5 ], [ 179.0, 47.0 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 179.0, 48.0, -179.0, 51.0 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 179.0, 51.0 ], [ 180.0, 49.5 ] ], [ [ -180.0, 49.5 ], [ -179.0, 48.0 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 177.0, 48.0, -175.0, 51.0 ], "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 177.0, 51.0 ], [ 177.0, 48.0 ], [ 180.0, 48.0 ], [ 180.0, 51.0 ], [ 177.0, 51.0 ] ] ], [ [ [ -180.0, 51.0 ], [ -180.0, 48.0 ], [ -175.0, 48.0 ], [ -175.0, 51.0 ], [ -180.0, 51.0 ] ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 177.0, 48.0, -175.0, 51.0 ], "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 177.0, 51.0 ], [ 177.0, 48.0 ], [ 180.0, 48.0 ], [ 180.0, 51.0 ], [ 177.0, 51.0 ] ] ], [ [ [ -180.0, 51.0 ], [ -180.0, 48.0 ], [ -175.0, 48.0 ], [ -175.0, 51.0 ], [ -180.0, 51.0 ] ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    return 'success'

###############################################################################
# Test RFC 7946 and reprojection


def ogr_geojson_57():
    if gdaltest.geojson_drv is None:
        return 'skip'
    if not ogrtest.have_geos():
        return 'skip'

    # Standard case: EPSG:32662: WGS 84 / Plate Carre
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((2000000 2000000,2000000 -2000000,-2000000 -2000000,-2000000 2000000,2000000 2000000))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ -17.9663057, -17.9663057, 17.9663057, 17.9663057 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ -17.9663057, -17.9663057, 17.9663057, 17.9663057 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 17.9663057, 17.9663057 ], [ -17.9663057, 17.9663057 ], [ -17.9663057, -17.9663057 ], [ 17.9663057, -17.9663057 ], [ 17.9663057, 17.9663057 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Polar case: EPSG:3995: WGS 84 / Arctic Polar Stereographic
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((2000000 2000000,2000000 -2000000,-2000000 -2000000,-2000000 2000000,2000000 2000000))'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((-2000000 -2000000,-1000000 -2000000,-1000000 2000000,-2000000 2000000,-2000000 -2000000))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ -180.0000000, 64.3861643, 180.0000000, 90.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ -180.0, 64.3861643, 180.0, 90.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 135.0, 64.3861643 ], [ 180.0, 71.7425119 ], [ 180.0, 90.0 ], [ -180.0, 90.0 ], [ -180.0, 71.7425119 ], [ -135.0, 64.3861643 ], [ -45.0, 64.3861643 ], [ 45.0, 64.3861643 ], [ 135.0, 64.3861643 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ -153.4349488, 64.3861643, -26.5650512, 69.6286694 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -45.0, 64.3861643 ], [ -26.5650512, 69.6286694 ], [ -153.4349488, 69.6286694 ], [ -135.0, 64.3861643 ], [ -45.0, 64.3861643 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Polar case: slice of spherical cap (not intersecting antimeridian, west hemisphere)
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((-2000000 2000000,0 0,-2000000 -2000000,-2000000 2000000))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ -135.0000000, 64.3861643, -45.0000000, 90.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ -135.0, 64.3861643, -45.0, 90.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -135.0, 64.3861643 ], [ -45.0, 64.3861643 ], [ -45.0, 90.0 ], [ -135.0, 90.0 ], [ -135.0, 64.3861643 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Polar case: slice of spherical cap (not intersecting antimeridian, east hemisphere)
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('MULTIPOLYGON(((2000000 2000000,0 0,2000000 -2000000,2000000 2000000)))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 45.0000000, 64.3861643, 135.0000000, 90.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 45.0, 64.3861643, 135.0, 90.0 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 135.0, 64.3861643 ], [ 135.0, 90.0 ], [ 45.0, 90.0 ], [ 45.0, 64.3861643 ], [ 135.0, 64.3861643 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Polar case: slice of spherical cap crossing the antimeridian
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((100000 100000,-100000 100000,0 0,100000 100000))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 135.0000000, 88.6984598, -135.0000000, 90.0000000 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 135.0, 88.6984598, -135.0, 90.0 ], "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 180.0, 89.0796531 ], [ 180.0, 90.0 ], [ 135.0, 88.6984598 ], [ 180.0, 89.0796531 ] ] ], [ [ [ -180.0, 90.0 ], [ -180.0, 89.0796531 ], [ -135.0, 88.6984598 ] ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Polar case: EPSG:3031: WGS 84 / Antarctic Polar Stereographic
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('MULTIPOLYGON(((2000000 2000000,2000000 -2000000,-2000000 -2000000,-2000000 2000000,2000000 2000000)))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ -180.0000000, -90.0000000, 180.0000000, -64.3861643 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ -180.0, -90.0, 180.0, -64.3861643 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 45.0, -64.3861643 ], [ -45.0, -64.3861643 ], [ -135.0, -64.3861643 ], [ -180.0, -71.7425119 ], [ -180.0, -90.0 ], [ 180.0, -90.0 ], [ 180.0, -71.7425119 ], [ 135.0, -64.3861643 ], [ 45.0, -64.3861643 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Antimeridian case: EPSG:32660: WGS 84 / UTM zone 60N with polygon and line crossing
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=utm +zone=60 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((670000 4000000,850000 4000000,850000 4100000,670000 4100000,670000 4000000))'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('MULTILINESTRING((670000 4000000,850000 4100000))'))
    lyr.CreateFeature(f)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('LINESTRING(670000 0,850000 0)'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 178.5275649, 0.0000000, -179.0681936, 37.0308258 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 178.8892102, 36.0816324, -179.0681936, 37.0308258 ], "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 180.0, 36.1071354 ], [ 180.0, 37.0082839 ], [ 178.9112998, 37.0308258 ], [ 178.8892102, 36.1298163 ], [ 180.0, 36.1071354 ] ] ], [ [ [ -179.0681936, 36.9810434 ], [ -180.0, 37.0082839 ], [ -180.0, 36.1071354 ], [ -179.1135277, 36.0816324 ], [ -179.0681936, 36.9810434 ] ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 178.8892102, 36.1298163, -179.0681936, 36.9810434 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 178.8892102, 36.1298163 ], [ 180.0, 36.5995612 ] ], [ [ -180.0, 36.5995612 ], [ -179.0681936, 36.9810434 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 178.5275649, 0.0, -179.8562277, 0.0 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 178.5275649, 0.0 ], [ 180.0, 0.0 ] ], [ [ -180.0, 0.0 ], [ -179.8562277, 0.0 ] ] ] } }
]
}
"""

    # with proj 4.9.3
    expected2 = """{
"type": "FeatureCollection",
"bbox": [ 178.5275649, 0.0000000, -179.0681936, 37.0308258 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 178.8892102, 36.0816324, -179.0681936, 37.0308258 ], "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -179.0681936, 36.9810434 ], [ -180.0, 37.0082839 ], [ -180.0, 36.1071354 ], [ -179.1135277, 36.0816324 ], [ -179.0681936, 36.9810434 ] ] ], [ [ [ 178.8892102, 36.1298163 ], [ 180.0, 36.1071354 ], [ 180.0, 37.0082839 ], [ 178.9112998, 37.0308258 ], [ 178.8892102, 36.1298163 ] ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 178.8892102, 36.1298163, -179.0681936, 36.9810434 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 178.8892102, 36.1298163 ], [ 180.0, 36.5995612 ] ], [ [ -180.0, 36.5995612 ], [ -179.0681936, 36.9810434 ] ] ] } },
{ "type": "Feature", "properties": { }, "bbox": [ 178.5275649, 0.0, -179.8562277, 0.0 ], "geometry": { "type": "MultiLineString", "coordinates": [ [ [ 178.5275649, 0.0 ], [ 180.0, 0.0 ] ], [ [ -180.0, 0.0 ], [ -179.8562277, 0.0 ] ] ] } }
]
}
"""

    if json.loads(got) != json.loads(expected) and json.loads(got) != json.loads(expected2):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    # Antimeridian case: EPSG:32660: WGS 84 / UTM zone 60N wit polygon on west of antimeridian
    src_ds = gdal.GetDriverByName('Memory').Create('', 0, 0, 0)
    sr = osr.SpatialReference()
    sr.SetFromUserInput('+proj=utm +zone=60 +datum=WGS84 +units=m +no_defs')
    lyr = src_ds.CreateLayer('test', srs=sr)
    f = ogr.Feature(lyr.GetLayerDefn())
    f.SetGeometry(ogr.CreateGeometryFromWkt('POLYGON((670000 4000000,700000 4000000,700000 4100000,670000 4100000,670000 4000000))'))
    lyr.CreateFeature(f)

    gdal.VectorTranslate('/vsimem/out.json', src_ds, format='GeoJSON', layerCreationOptions=['WRITE_NAME=NO', 'RFC7946=YES', 'WRITE_BBOX=YES'])

    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    expected = """{
"type": "FeatureCollection",
"bbox": [ 178.8892102, 36.1240958, 179.2483693, 37.0308258 ],
"features": [
{ "type": "Feature", "properties": { }, "bbox": [ 178.8892102, 36.1240958, 179.2483693, 37.0308258 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 178.8892102, 36.1298163 ], [ 179.2223914, 36.1240958 ], [ 179.2483693, 37.0249155 ], [ 178.9112998, 37.0308258 ], [ 178.8892102, 36.1298163 ] ] ] } }
]
}
"""
    if json.loads(got) != json.loads(expected):
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    return 'success'

###############################################################################
# Test using the name member of FeatureCollection


def ogr_geojson_58():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('{ "type": "FeatureCollection", "name": "layer_name", "features": []}')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayerByName('layer_name')
    if lyr is None:
        gdaltest.post_reason('Missing layer called layer_name')
        return 'fail'
    ds = None

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_58.json')
    lyr = ds.CreateLayer('foo')
    ds = None
    ds = ogr.Open('/vsimem/ogr_geojson_58.json')
    if ds.GetLayerByName('foo') is None:
        gdaltest.post_reason('Missing layer called foo')
        return 'fail'
    ds = None
    gdal.Unlink('/vsimem/ogr_geojson_58.json')

    return 'success'

###############################################################################
# Test using the description member of FeatureCollection


def ogr_geojson_59():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = ogr.Open('{ "type": "FeatureCollection", "description": "my_description", "features": []}')
    if ds is None:
        gdaltest.post_reason('Failed to open datasource')
        return 'fail'

    lyr = ds.GetLayer(0)
    if lyr.GetMetadataItem('DESCRIPTION') != 'my_description':
        gdaltest.post_reason('Did not get DESCRIPTION')
        return 'fail'
    ds = None

    ds = ogr.GetDriverByName('GeoJSON').CreateDataSource('/vsimem/ogr_geojson_59.json')
    lyr = ds.CreateLayer('foo', options=['DESCRIPTION=my desc'])
    ds = None
    ds = ogr.Open('/vsimem/ogr_geojson_59.json')
    lyr = ds.GetLayerByName('foo')
    if lyr.GetMetadataItem('DESCRIPTION') != 'my desc':
        gdaltest.post_reason('Did not get DESCRIPTION')
        return 'fail'
    ds = None
    gdal.Unlink('/vsimem/ogr_geojson_59.json')

    return 'success'

###############################################################################
# Test null vs unset field


def ogr_geojson_60():
    if gdaltest.geojson_drv is None:
        return 'skip'

    ds = gdal.OpenEx("""{ "type": "FeatureCollection", "features": [
{ "type": "Feature", "properties" : { "foo" : "bar" } },
{ "type": "Feature", "properties" : { "foo": null } },
{ "type": "Feature", "properties" : {  } } ] }""")
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f['foo'] != 'bar':
        gdaltest.post_reason('failure')
        f.DumpReadable()
        return 'fail'
    f = lyr.GetNextFeature()
    if not f.IsFieldNull('foo'):
        gdaltest.post_reason('failure')
        f.DumpReadable()
        return 'fail'
    f = lyr.GetNextFeature()
    if f.IsFieldSet('foo'):
        gdaltest.post_reason('failure')
        f.DumpReadable()
        return 'fail'

    # Test writing side
    gdal.VectorTranslate('/vsimem/ogr_geojson_60.json', ds, format='GeoJSON')

    fp = gdal.VSIFOpenL('/vsimem/ogr_geojson_60.json', 'rb')
    data = gdal.VSIFReadL(1, 10000, fp).decode('ascii')
    gdal.VSIFCloseL(fp)

    gdal.Unlink('/vsimem/ogr_geojson_60.json')
    if data.find('"properties": { "foo": "bar" }') < 0 or \
       data.find('"properties": { "foo": null }') < 0 or \
       data.find('"properties": { }') < 0:
        gdaltest.post_reason('failure')
        print(data)
        return 'fail'

    return 'success'


###############################################################################
# Test corner cases

def ogr_geojson_61():

    # Invalid JSon
    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_61.json',
                           """{ "type": "FeatureCollection", "features": [""")
    with gdaltest.error_handler():
        ds = gdal.OpenEx('/vsimem/ogr_geojson_61.json')
    if ds is not None:
        gdaltest.post_reason('failure')
        return 'fail'
    gdal.Unlink('/vsimem/ogr_geojson_61.json')

    # Invalid single geometry
    with gdaltest.error_handler():
        ds = gdal.OpenEx("""{ "type": "Point", "x" : { "coordinates" : null } } """)
    if ds is not None:
        gdaltest.post_reason('failure')
        return 'fail'

    # Empty property name
    gdal.FileFromMemBuffer('/vsimem/ogr_geojson_61.json',
                           """{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": {"": 1}, "geometry": null }] }""")
    ds = gdal.OpenEx('/vsimem/ogr_geojson_61.json')
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f.GetField("") != 1:
        gdaltest.post_reason('failure')
        return 'fail'
    ds = None
    gdal.Unlink('/vsimem/ogr_geojson_61.json')

    return 'success'

###############################################################################
# Test crs object


def ogr_geojson_62():

    # crs type=name tests
    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name" }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name", "properties":null }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name", "properties":1 }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name", "properties":{"name":null} }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name", "properties":{"name":1} }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name", "properties":{"name":"x"} }, "features":[] }""")

    ds = gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"name", "properties":{"name": "urn:ogc:def:crs:EPSG::32631"} }, "features":[] }""")
    lyr = ds.GetLayer(0)
    srs = lyr.GetSpatialRef()
    if srs.ExportToWkt().find('32631') < 0:
        gdaltest.post_reason('failure')
        return 'fail'

    # crs type=EPSG (not even documented in GJ2008 spec!) tests. Just for coverage completness
    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG" }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG", "properties":null }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG", "properties":1 }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG", "properties":{"code":null} }, "features":[] }""")

    with gdaltest.error_handler():
        gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG", "properties":{"code":1} }, "features":[] }""")

    with gdaltest.error_handler():
        gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG", "properties":{"code":"x"} }, "features":[] }""")

    ds = gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"EPSG", "properties":{"code": 32631} }, "features":[] }""")
    lyr = ds.GetLayer(0)
    srs = lyr.GetSpatialRef()
    if srs.ExportToWkt().find('32631') < 0:
        gdaltest.post_reason('failure')
        return 'fail'

    # crs type=link tests
    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"link" }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"link", "properties":null }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"link", "properties":1 }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"link", "properties":{"href":null} }, "features":[] }""")

    with gdaltest.error_handler():
        gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"link", "properties":{"href":1} }, "features":[] }""")

    with gdaltest.error_handler():
        gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"link", "properties":{"href": "1"} }, "features":[] }""")

    # crs type=OGC (not even documented in GJ2008 spec!) tests. Just for coverage completness
    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC" }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC", "properties":null }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC", "properties":1 }, "features":[] }""")

    gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC", "properties":{"urn":null} }, "features":[] }""")

    with gdaltest.error_handler():
        gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC", "properties":{"urn":1} }, "features":[] }""")

    with gdaltest.error_handler():
        gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC", "properties":{"urn":"x"} }, "features":[] }""")

    ds = gdal.OpenEx("""{ "type": "FeatureCollection", "crs": { "type":"OGC", "properties":{"urn": "urn:ogc:def:crs:EPSG::32631"} }, "features":[] }""")
    lyr = ds.GetLayer(0)
    srs = lyr.GetSpatialRef()
    if srs.ExportToWkt().find('32631') < 0:
        gdaltest.post_reason('failure')
        return 'fail'

    return 'success'

###############################################################################
# Extensive test of field tye promotion


def ogr_geojson_63():

    ds_ref = ogr.Open('data/test_type_promotion_ref.json')
    lyr_ref = ds_ref.GetLayer(0)
    ds = ogr.Open('data/test_type_promotion.json')
    lyr = ds.GetLayer(0)
    return ogrtest.compare_layers(lyr, lyr_ref)

###############################################################################
# Test exporting XYM / XYZM (#6935)


def ogr_geojson_64():

    g = ogr.CreateGeometryFromWkt('POINT ZM(1 2 3 4)')
    if ogrtest.check_feature_geometry(ogr.CreateGeometryFromJson(g.ExportToJson()),
                                      ogr.CreateGeometryFromWkt('POINT Z(1 2 3)')) != 0:
        return 'fail'

    g = ogr.CreateGeometryFromWkt('POINT M(1 2 3)')
    if ogrtest.check_feature_geometry(ogr.CreateGeometryFromJson(g.ExportToJson()),
                                      ogr.CreateGeometryFromWkt('POINT (1 2)')) != 0:
        return 'fail'

    g = ogr.CreateGeometryFromWkt('LINESTRING ZM(1 2 3 4,5 6 7 8)')
    if ogrtest.check_feature_geometry(ogr.CreateGeometryFromJson(g.ExportToJson()),
                                      ogr.CreateGeometryFromWkt('LINESTRING Z(1 2 3,5 6 7)')) != 0:
        return 'fail'

    g = ogr.CreateGeometryFromWkt('LINESTRING M(1 2 3,4 5 6)')
    if ogrtest.check_feature_geometry(ogr.CreateGeometryFromJson(g.ExportToJson()),
                                      ogr.CreateGeometryFromWkt('LINESTRING (1 2,4 5)')) != 0:
        return 'fail'

    return 'success'

###############################################################################
# Test feature geometry CRS when CRS set on the FeatureCollection
# See https://github.com/r-spatial/sf/issues/449#issuecomment-319369945


def ogr_geojson_65():

    ds = ogr.Open("""{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32631" } },
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [500000,4500000]},
"properties": {
}}]}""")
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    srs = f.GetGeometryRef().GetSpatialReference()
    pcs = int(srs.GetAuthorityCode('PROJCS'))
    if pcs != 32631:
        gdaltest.post_reason('Spatial reference for individual geometry was not valid')
        return 'fail'

    return 'success'

###############################################################################
# Test features with properties not being a dictionary


def ogr_geojson_66():

    ds = ogr.Open("""{
"type": "FeatureCollection",
"features": [
{
    "type": "Feature",
    "geometry": null,
    "properties": null
},
{
    "type": "Feature",
    "geometry": null,
    "properties": []
}
]}""")
    lyr = ds.GetLayer(0)
    if lyr.GetLayerDefn().GetFieldCount() != 0:
        return 'fail'

    return 'success'


###############################################################################
# Test reading GeoJSON files starting with {"features":[{"geometry":.... (#7198)

def ogr_geojson_67():

    ds = ogr.Open('data/grenada.geojson')
    if ds is None:
        gdaltest.post_reason('fail')
        return 'fail'
    if ds.GetDriver().GetName() != 'GeoJSON':
        gdaltest.post_reason('fail')
        return 'fail'
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    return 'success'


###############################################################################
# Test reading ESRIJSON files starting with {"features":[{"geometry":.... (#7198)

def ogr_geojson_68():

    ds = ogr.Open('data/esrijsonstartingwithfeaturesgeometry.json')
    if ds is None:
        gdaltest.post_reason('fail')
        return 'fail'
    if ds.GetDriver().GetName() != 'ESRIJSON':
        gdaltest.post_reason('fail')
        return 'fail'
    lyr = ds.GetLayer(0)
    if lyr.GetFeatureCount() != 1:
        gdaltest.post_reason('fail')
        return 'fail'
    return 'success'

###############################################################################


def ogr_geojson_id_field_and_id_type():

    gdal.VectorTranslate('/vsimem/out.json', 'data/poly.shp', options='-f GeoJSON -lco ID_TYPE=String -preserve_fid -limit 1 -fid 2')
    got = read_file('/vsimem/out.json')
    if got.find('"id": "2", "properties": { "AREA": 261752.781, "EAS_ID": 171, "PRFEDEA": "35043414" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    gdal.VectorTranslate('/vsimem/out.json', 'data/poly.shp', options='-f GeoJSON -lco ID_TYPE=Integer -preserve_fid -limit 1 -fid 2')
    got = read_file('/vsimem/out.json')
    if got.find('"id": 2, "properties": { "AREA": 261752.781, "EAS_ID": 171, "PRFEDEA": "35043414" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    gdal.VectorTranslate('/vsimem/out.json', 'data/poly.shp', format='GeoJSON', layerCreationOptions=['ID_FIELD=EAS_ID'], limit=1)
    got = read_file('/vsimem/out.json')
    if got.find('"id": 168, "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    src_ds = gdal.OpenEx('/vsimem/out.json', open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out2.json', src_ds, format='GeoJSON')
    src_ds = None
    got = read_file('/vsimem/out2.json')
    gdal.Unlink('/vsimem/out2.json')
    if got.find('"id": 168, "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    src_ds = gdal.OpenEx('/vsimem/out.json', open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out2.json', src_ds, format='GeoJSON', layerCreationOptions=['ID_TYPE=String'])
    src_ds = None
    got = read_file('/vsimem/out2.json')
    gdal.Unlink('/vsimem/out2.json')
    if got.find('"id": "168", "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    src_ds = gdal.OpenEx('/vsimem/out.json', open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out2.json', src_ds, format='GeoJSON', layerCreationOptions=['ID_TYPE=Integer'])
    src_ds = None
    got = read_file('/vsimem/out2.json')
    gdal.Unlink('/vsimem/out2.json')
    if got.find('"id": 168, "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    gdal.Unlink('/vsimem/out.json')

    gdal.VectorTranslate('/vsimem/out.json', 'data/poly.shp', format='GeoJSON', layerCreationOptions=['ID_FIELD=EAS_ID', 'ID_TYPE=String'], limit=1)
    got = read_file('/vsimem/out.json')
    if got.find('"id": "168", "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    src_ds = gdal.OpenEx('/vsimem/out.json', open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out2.json', src_ds, format='GeoJSON')
    src_ds = None
    got = read_file('/vsimem/out2.json')
    gdal.Unlink('/vsimem/out2.json')
    if got.find('"id": "168", "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    src_ds = gdal.OpenEx('/vsimem/out.json', open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out2.json', src_ds, format='GeoJSON', layerCreationOptions=['ID_TYPE=String'])
    src_ds = None
    got = read_file('/vsimem/out2.json')
    gdal.Unlink('/vsimem/out2.json')
    if got.find('"id": "168", "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    src_ds = gdal.OpenEx('/vsimem/out.json', open_options=['NATIVE_DATA=YES'])
    gdal.VectorTranslate('/vsimem/out2.json', src_ds, format='GeoJSON', layerCreationOptions=['ID_TYPE=Integer'])
    src_ds = None
    got = read_file('/vsimem/out2.json')
    gdal.Unlink('/vsimem/out2.json')
    if got.find('"id": 168, "properties": { "AREA": 215229.266, "PRFEDEA": "35043411" }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    gdal.Unlink('/vsimem/out.json')

    gdal.VectorTranslate('/vsimem/out.json', 'data/poly.shp', format='GeoJSON', layerCreationOptions=['ID_FIELD=PRFEDEA'], limit=1)
    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    if got.find('"id": "35043411", "properties": { "AREA": 215229.266, "EAS_ID": 168 }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    gdal.VectorTranslate('/vsimem/out.json', 'data/poly.shp', format='GeoJSON', layerCreationOptions=['ID_FIELD=PRFEDEA', 'ID_TYPE=Integer'], limit=1)
    got = read_file('/vsimem/out.json')
    gdal.Unlink('/vsimem/out.json')
    if got.find('"id": 35043411, "properties": { "AREA": 215229.266, "EAS_ID": 168 }') < 0:
        gdaltest.post_reason('fail')
        print(got)
        return 'fail'

    return 'success'

###############################################################################


def ogr_geojson_geom_export_failure():

    g = ogr.CreateGeometryFromWkt('POINT EMPTY')
    geojson = g.ExportToJson()
    if geojson is not None:
        gdaltest.post_reason('fail')
        print(geojson)
        return 'fail'

    g = ogr.CreateGeometryFromWkt('GEOMETRYCOLLECTION(TIN EMPTY)')
    geojson = json.loads(g.ExportToJson())
    if geojson != {"type": "GeometryCollection", "geometries": None}:
        gdaltest.post_reason('fail')
        print(geojson)
        return 'fail'

    g = ogr.Geometry(ogr.wkbLineString)
    g.AddPoint_2D(float('nan'), 0)
    with gdaltest.error_handler():
        geojson = g.ExportToJson()
    if geojson is not None:
        gdaltest.post_reason('fail')
        print(geojson)
        return 'fail'

    g = ogr.Geometry(ogr.wkbPolygon)
    lr = ogr.Geometry(ogr.wkbLinearRing)
    lr.AddPoint_2D(0, 0)
    lr.AddPoint_2D(0, 1)
    lr.AddPoint_2D(1, 1)
    lr.AddPoint_2D(0, 0)
    g.AddGeometry(lr)
    lr = ogr.Geometry(ogr.wkbLinearRing)
    lr.AddPoint_2D(0, 0)
    lr.AddPoint_2D(float('nan'), 1)
    lr.AddPoint_2D(1, 1)
    lr.AddPoint_2D(0, 0)
    g.AddGeometry(lr)
    with gdaltest.error_handler():
        geojson = g.ExportToJson()
    if geojson is not None:
        gdaltest.post_reason('fail')
        print(geojson)
        return 'fail'

    return 'success'

###############################################################################


def ogr_geojson_starting_with_crs():

    ds = ogr.Open("""{
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::32631" } },
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [500000,4500000]},
"properties": {
}}]}""")
    if ds is None:
        return 'fail'

    return 'success'

###############################################################################
# Test we properly flush the file in SyncToDisk() in append situations


def ogr_geojson_append_flush():

    tmpfilename = 'tmp/ogr_geojson_append_flush.json'
    f = gdal.VSIFOpenL(tmpfilename, 'wb')
    content = """{
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "properties": { "x": 1, "y": 2, "z": 3, "w": 4 }, "geometry": { "type": "Point", "coordinates": [ 0, 0 ] } } ] }"""
    gdal.VSIFWriteL(content, 1, len(content), f)
    gdal.VSIFCloseL(f)

    ds = ogr.Open(tmpfilename, update=1)
    lyr = ds.GetLayer(0)
    f = ogr.Feature(lyr.GetLayerDefn())
    f['x'] = 10
    lyr.CreateFeature(f)
    lyr.SyncToDisk()

    ds2 = ogr.Open(tmpfilename, update=1)
    lyr = ds2.GetLayer(0)
    lyr.GetNextFeature()
    f = lyr.GetNextFeature()
    if f is None or f['x'] != 10:
        return 'fail'

    ds = None
    ds2 = None
    gdal.Unlink(tmpfilename)

    return 'success'

###############################################################################


def ogr_esrijson_without_geometryType():

    ds = ogr.Open("""{
  "spatialReference" : {
    "wkid" : 4326
  },
  "fields": [],
  "fieldAliases": {},
  "features" : [
    {
      "geometry" : {
        "x" : 2,
        "y" : 49
      },
      "attributes" : {
      }
    }
  ]
}
""")
    lyr = ds.GetLayer(0)
    f = lyr.GetNextFeature()
    if f.GetGeometryRef() is None:
        return 'fail'

    return 'success'

###############################################################################


def ogr_geojson_cleanup():

    gdal.SetConfigOption('CPL_CURL_ENABLE_VSIMEM', None)

    try:
        if gdaltest.tests is not None:
            gdal.PushErrorHandler('CPLQuietErrorHandler')
            for i in range(len(gdaltest.tests)):

                fname = os.path.join('tmp', gdaltest.tests[i][0] + '.geojson')
                ogr.GetDriverByName('GeoJSON').DeleteDataSource(fname)

                fname = os.path.join('tmp', gdaltest.tests[i][0] + '.geojson.gz')
                gdal.Unlink(fname)

                fname = os.path.join('tmp', gdaltest.tests[i][0] + '.geojson.gz.properties')
                gdal.Unlink(fname)

            gdal.PopErrorHandler()

        gdaltest.tests = None
    except:
        pass

    try:
        os.remove('tmp/out_ogr_geojson_14.geojson')
    except OSError:
        pass

    for f in gdal.ReadDir('/vsimem/geojson'):
        gdal.Unlink('/vsimem/geojson/' + f)

    return 'success'


gdaltest_list = [
    ogr_geojson_1,
    ogr_geojson_2,
    ogr_geojson_3,
    ogr_geojson_4,
    ogr_geojson_5,
    ogr_geojson_6,
    ogr_geojson_7,
    ogr_geojson_8,
    ogr_geojson_9,
    ogr_geojson_10,
    ogr_geojson_11,
    ogr_geojson_12,
    ogr_geojson_13,
    ogr_geojson_14,
    ogr_geojson_15,
    ogr_geojson_16,
    ogr_geojson_17,
    ogr_geojson_18,
    ogr_geojson_19,
    ogr_geojson_20,
    ogr_geojson_21,
    ogr_geojson_22,
    ogr_geojson_23,
    ogr_geojson_24,
    ogr_geojson_25,
    ogr_geojson_26,
    ogr_geojson_27,
    ogr_geojson_28,
    ogr_geojson_29,
    ogr_geojson_30,
    ogr_geojson_31,
    ogr_geojson_32,
    ogr_geojson_33,
    ogr_geojson_34,
    ogr_geojson_35,
    ogr_geojson_36,
    ogr_geojson_37,
    ogr_geojson_38,
    ogr_geojson_39,
    ogr_geojson_40,
    ogr_geojson_41,
    ogr_geojson_42,
    ogr_geojson_43,
    ogr_geojson_44,
    ogr_geojson_45,
    ogr_geojson_46,
    ogr_geojson_47,
    ogr_geojson_48,
    ogr_geojson_49,
    ogr_geojson_50,
    ogr_geojson_51,
    ogr_geojson_52,
    ogr_geojson_53,
    ogr_geojson_54,
    ogr_geojson_55,
    ogr_geojson_56,
    ogr_geojson_57,
    ogr_geojson_58,
    ogr_geojson_59,
    ogr_geojson_60,
    ogr_geojson_61,
    ogr_geojson_62,
    ogr_geojson_63,
    ogr_geojson_64,
    ogr_geojson_65,
    ogr_geojson_66,
    ogr_geojson_67,
    ogr_geojson_68,
    ogr_geojson_id_field_and_id_type,
    ogr_geojson_geom_export_failure,
    ogr_geojson_starting_with_crs,
    ogr_geojson_append_flush,
    ogr_esrijson_without_geometryType,
    ogr_geojson_cleanup]

if __name__ == '__main__':

    gdaltest.setup_run('ogr_geojson')

    gdaltest.run_tests(gdaltest_list)

    gdaltest.summarize()
