Skip to content

Instantly share code, notes, and snippets.

@aaronpk
Last active March 20, 2024 11:54
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save aaronpk/0252426d5161bc9650d8 to your computer and use it in GitHub Desktop.
Save aaronpk/0252426d5161bc9650d8 to your computer and use it in GitHub Desktop.
How to compile and install spatialite on iOS

Spatialite for iOS

Install compiler dependencies

brew install automake autoconf libtool libxml2 pkg-config
brew link libxml2

Build libspatialite

git clone https://github.com/gstf/libspatialite-ios.git
cd libspatialite-ios
make

The result is a folder, lib, containing several .a files, as well as an include folder containing many .h files.

Create a new XCode project

Copy the libspatialite binaries and "include" folder into a folder named "libspatialite" in the XCode project folder.

filesystem

In the XCode project's "Build Settings", you'll need to set the library and header search paths:

Library Search Paths: $(PROJECT_DIR)/AppName/libspatialite Header Search Paths: $(PROJECT_DIR)/AppName/libspatialite/include

search paths

Drag the .a files into your project.

From the "Build Phases" window, add the following to the section "Link Binary With Libraries":

  • libz.dylib
  • libxml2.2.dylib
  • libc++.dylib
  • libcharset.1.0.0.dylib
  • libiconv.dylib

XCode Project

Now you should be able to use spatialite! To test if everything worked, just make your AppDelegate output the spatialite version.

Add the following to AppDelegate.m

#include <sqlite3.h>
#include <spatialite/gaiageo.h>
#include <spatialite.h>

In your application:didFinishLaunchingWithOptions: method, add:

spatialite_init (0);
printf("Spatialite version: %s\n", spatialite_version());

Compile and run and you should see the version output in the console!

@smbkr
Copy link

smbkr commented Oct 4, 2020

Hi Aaron, I came across this gist when looking for some info on using Spatialite with iOS. Thanks for providing the build instructions! I just wondered how you're using this on iOS?

I followed your instructions and can call spatialite_init just fine, get the version number, etc, but when I connect to a database and try to use Spatialite functions like MakePoint or Transform etc, I get "function not found", so it seems like I'm connecting to a vanilla Sqlite DB.

Is there any extra step needed to initialise a connection with Spatialite? If you've got an open source project using this feel free to just point me to the repo and I can study at my own page.

Thanks!

@aaronpk
Copy link
Author

aaronpk commented Oct 4, 2020

I honestly have no idea if this works anymore, this is like 5 years old! I am pretty sure the project I was using this on is also dead now too. Sorry!

@smbkr
Copy link

smbkr commented Oct 4, 2020

Thanks for the reply, I appreciate it! After some more research, it looks like the way that Spatialite is initialised has changed which is where I was running into problems.

@dissymmetryoflift
Copy link

Did you ever figure out a way to get spatialite working on iOS? Or is there another library that is good for spatial queries?

@smbkr
Copy link

smbkr commented Nov 13, 2020

Did you ever figure out a way to get spatialite working on iOS? Or is there another library that is good for spatial queries?

I did, I will try to write up some instructions/provide an example this weekend if I have time.

Edit: https://github.com/smbkr/libspatialite-ios see here for instructions getting this working with Xcode 12 and Swift

@Vaibhav-Narkhede
Copy link

Did you ever figure out a way to get spatialite working on iOS? Or is there another library that is good for spatial queries?

I did, I will try to write up some instructions/provide an example this weekend if I have time.

Edit: https://github.com/smbkr/libspatialite-ios see here for instructions getting this working with Xcode 12 and Swift

Hi Smbkr, I tried your library it's working but it has old version of libspatialite-4.4.0-RC1. Updated version is 5.0.1 I tried to update libspatialite URL in Makefile but it's not working. Do you have any plan to update this library?

@smbkr
Copy link

smbkr commented Jul 19, 2021

Did you ever figure out a way to get spatialite working on iOS? Or is there another library that is good for spatial queries?

I did, I will try to write up some instructions/provide an example this weekend if I have time.
Edit: https://github.com/smbkr/libspatialite-ios see here for instructions getting this working with Xcode 12 and Swift

Hi Smbkr, I tried your library it's working but it has old version of libspatialite-4.4.0-RC1. Updated version is 5.0.1 I tried to update libspatialite URL in Makefile but it's not working. Do you have any plan to update this library?

Hi Vaibhav,

I am not using it currently so I have no plans myself to update it. I imagine that bumping libspatialite up to v5 means you'll need to update its dependencies too (geos, proj, maybe others?) and possibly change some of the build steps in the makefile. Feel free to fork it!

@mrclayman
Copy link

@smbkr, @Vaibhav-Narkhede, I forked the repo and started meddling with the Makefile to bring in the newest versions of the packages and add new dependencies as well. I will need to be able to incorporate spatialite in my app.

I feel like I should be on the right track, but I currently have an issue compiling the rttopo library that appears to be a dependency of the newest version of spatialite. My problem is that the build of the library fails at the configure stage because a check of the geos_c library for a specific symbol doesn't pass. This is the relevant snippet:

Undefined symbols for architecture armv7:
  "_GEOSContext_setErrorMessageHandler_r", referenced from:
      _main in conftest-ad3289.o
ld: symbol(s) not found for architecture armv7

I checked the built library archive file using nm build/armv7/lib/libgeos_c.a and did find the symbol there. I am also passing geos-config from the bin directory in build/armv7 to make sure the right one is picked. I am kind of suspecting that it's maybe picking up the libgeos_c library I have in my system in /usr/local/lib because I have PostgreSQL with PostGIS installed as well, but I am not entirely sure about this.

I have my fork here so if any one of you guys want to take a look and maybe give a hint, I'd be grateful. 😊

@mrclayman
Copy link

mrclayman commented Aug 15, 2021

Just to follow up on my previous message, I was able to make it all work eventually. I found out librttopo (as well as libtiff) were optional so I set configure scripts to stop looking for them, but had to add custom libsqlite3.a build step (for which there already were targets, but were not used) because the default sqlite3 headers in Xcode did not seem to provide functions for loading extensions.

I am not very familiar with all this on Mac OS (been using Linux for the last 15 years, though), so I hope I got it right. At any rate, I am going to include the output of the build in my project and see how that goes. I also updated my fork with the new changes so feel free to check them out.

@Vaibhav-Narkhede
Copy link

Thanks @mrclayman I tried your updated repository. It's complied successfully.

@iulian0512
Copy link

iulian0512 commented Apr 27, 2022

@smbkr, @Vaibhav-Narkhede, I forked the repo and started meddling with the Makefile to bring in the newest versions of the packages and add new dependencies as well. I will need to be able to incorporate spatialite in my app.

I feel like I should be on the right track, but I currently have an issue compiling the rttopo library that appears to be a dependency of the newest version of spatialite. My problem is that the build of the library fails at the configure stage because a check of the geos_c library for a specific symbol doesn't pass. This is the relevant snippet:

Undefined symbols for architecture armv7:
  "_GEOSContext_setErrorMessageHandler_r", referenced from:
      _main in conftest-ad3289.o
ld: symbol(s) not found for architecture armv7

I checked the built library archive file using nm build/armv7/lib/libgeos_c.a and did find the symbol there. I am also passing geos-config from the bin directory in build/armv7 to make sure the right one is picked. I am kind of suspecting that it's maybe picking up the libgeos_c library I have in my system in /usr/local/lib because I have PostgreSQL with PostGIS installed as well, but I am not entirely sure about this.

I have my fork here so if any one of you guys want to take a look and maybe give a hint, I'd be grateful. 😊

i looked in the config.log of rttopo and it tries to compile

| char GEOSContext_setErrorMessageHandler_r ();
| int
| main (void)
| {
| return GEOSContext_setErrorMessageHandler_r ();
|   ;
|   return 0;
| }

GEOSContext_setErrorMessageHandler_r
documented here https://libgeos.org/doxygen/geos__c_8h.html#a68b1a9f6da8fc95c4eea5433768afa50
takes 3 parameters so no wonder it fails to link.
what i did is i removed the entire section starting from
Ensure we can link against libgeos_c in configure file in librttopo

@iulian0512
Copy link

iulian0512 commented Apr 27, 2022

Just to follow up on my previous message, I was able to make it all work eventually. I found out librttopo (as well as libtiff) were optional so I set configure scripts to stop looking for them, but had to add custom libsqlite3.a build step (for which there already were targets, but were not used) because the default sqlite3 headers in Xcode did not seem to provide functions for loading extensions.

I am not very familiar with all this on Mac OS (been using Linux for the last 15 years, though), so I hope I got it right. At any rate, I am going to include the output of the build in my project and see how that goes. I also updated my fork with the new changes so feel free to check them out.

not having rttopo you cannot use function such as ST_Area see https://www.gaia-gis.it/gaia-sins/spatialite-sql-5.0.0.html#p14d

@Tybion
Copy link

Tybion commented Feb 9, 2024

Getting started? - example Swift code?

Seems that example code is not easy to find. The following is my attempt, and when I run it, it seems to work - and I don't get an error message saying that InitSpatialMetaData is not recognised.

Is this correct? Is there anything else I should know?

let documentsUrl =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let docsPath = documentsUrl.path  // no trailing slash
print(docsPath)
var db: OpaquePointer?
if sqlite3_open(docsPath + "/spatialtest3.sqlite", &db) != SQLITE_OK {
     print("Unable to open database - \(docsPath).")
}
var spc: OpaquePointer?
spatialite_initialize()
print("Spatialite version: \(spatialite_version()!)")
spatialite_init_ex(db, &spc, 1)

.. and later ..

let sqlStr = "SELECT InitSpatialMetaData('WGS84');"
// .. and more code to run the query against spatialtest3.sqlite - the query returns nil ..

@Tybion
Copy link

Tybion commented Feb 10, 2024

To follow on from my question - I now have fully working Swift code - that might be useful to someone starting out, like me. It creates a .sqlite file, adds the spatialite metadata structure, creates a table, adds a geometry field to that table, inserts a row (using ST_GeomFromText(), and retrieves that same row to show that it all works. Feel free to add it to any documentation. You can paste it into any new project. I used Xcode 15.2.

import SQLite3
import Spatial
//------other code ----------

// Thank you to Kodeco for the sqlite3 code ..
// https://www.kodeco.com/6620276-sqlite-with-swift-tutorial-getting-started

let documentsUrl =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let docsPath = documentsUrl.path  // no trailing slash
let shortname =  "spatialtest6.sqlite"

let fullname = docsPath + "/" + shortname
           
var db: OpaquePointer?
print(shortname)
if sqlite3_open(fullname, &db) != SQLITE_OK {
   print("Unable to open database - \(shortname).")
}

var spconnect: OpaquePointer?
spatialite_initialize()
print("Spatialite version: \(spatialite_version()!)")
spatialite_init_ex(db, &spconnect, 1)

//    spatialite_init_ex((sqlite3 *) sqliteHandle, spatialiteConnection, 0);
let tableName = "features"
let createTableString = """
CREATE TABLE \(tableName) (
id integer PRIMARY KEY,
title text,
descr text);
"""
var stmnt: OpaquePointer?
print(createTableString)
if sqlite3_prepare_v2(db, createTableString, -1, &stmnt, nil) == SQLITE_OK {
    if sqlite3_step(stmnt) == SQLITE_DONE {
      print("Table created.")
    } else {
      print("Table not created.")
    }
} else {
    print("Statement not prepared.")
}

var stmnt4: OpaquePointer?
let sqlStr = "SELECT InitSpatialMetaData('WGS84');"
print(sqlStr)
if sqlite3_prepare_v2(db, sqlStr, -1, &stmnt4, nil) == SQLITE_OK {
    if sqlite3_step(stmnt4) == SQLITE_ROW {
        let result = sqlite3_column_int(stmnt4, 0)
        print("Query Result: \(result)")
    } else {
        print("Query returned no results.")
    }
} else {
    let errorMessage = String(cString: sqlite3_errmsg(db))
    print("Query is not prepared \(errorMessage)")
}

let sqlSpatial = [
        "SELECT AddGeometryColumn('\(tableName)', 'geom', 4326, 'POINT', 'XY');",
        "SELECT CreateSpatialIndex('\(tableName)', 'geom');"]
for sql in sqlSpatial {
    var stmt: OpaquePointer?
    print(sql)
    if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK {
        if sqlite3_step(stmt) == SQLITE_ROW {
            let result = sqlite3_column_int(stmt, 0)
            print("Query Result: \(result)")
        } else {
            print("Query returned no results.")
        }
    } else {
        let errorMessage = String(cString: sqlite3_errmsg(db))
        print("Query is not prepared \(errorMessage)")
    }
}

sqlite3_finalize(stmnt)
let insertStr = "INSERT INTO \(tableName) (id, title, descr, geom) " +
        "VALUES ( ?, ?, ?, ST_GeomFromText('POINT(126.0 -34.0)', 4326) );"
print(insertStr)
var stmnt2: OpaquePointer?
if sqlite3_prepare_v2(db, insertStr, -1, &stmnt2, nil) == SQLITE_OK {
    let id: Int32 = 0
    let title: NSString = "Pelican"
    let descr: NSString = "Likes fish"
    sqlite3_bind_int(stmnt2, 1, id)
    sqlite3_bind_text(stmnt2, 2, title.utf8String, -1, nil)
    sqlite3_bind_text(stmnt2, 3, descr.utf8String, -1, nil)
    if sqlite3_step(stmnt2) == SQLITE_DONE {
        print("Inserted row.")
    } else {
      print("Could not insert row.")
    }
} else {
    print("Insert statement not prepared.")
}
sqlite3_finalize(stmnt2)

var queryStr = "SELECT id, title, descr, ST_AsText(geom) AS wkt FROM features;"
print(queryStr)
var stmnt3: OpaquePointer?
if sqlite3_prepare_v2(db, queryStr, -1, &stmnt3, nil) == SQLITE_OK {
    if sqlite3_step(stmnt3) == SQLITE_ROW {
        let id = sqlite3_column_int(stmnt3, 0)
        guard let result = sqlite3_column_text(stmnt3, 1) else {
            print("Query result is nil")
            return
        }
        let title = String(cString: result)
        let descr = String(cString: sqlite3_column_text(stmnt3, 2))
        let wkt = String(cString: sqlite3_column_text(stmnt3, 3))
        print("Query Result: \(id), \(title), \(descr), \(wkt)")
    } else {
        print("Query returned no results.")
    }
} else {
    let errorMessage = String(cString: sqlite3_errmsg(db))
    print("Query is not prepared \(errorMessage)")
}
sqlite3_finalize(stmnt3) 

@Tybion
Copy link

Tybion commented Feb 11, 2024

The same steps, but using the GRDB wrapper - https://github.com/groue/GRDB.swift - MUCH tidier - and shorter ..
(Feel free to use in any doco - fully working.)

import SQLite3
import Spatial
import GRDB

// ------- other code -------

let fileManager = FileManager.default
let documentsUrl =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let docsPath = documentsUrl.path  // no trailing slash
let shortname =  "spatialtest.sqlite"
let tablename = shortname.replacingOccurrences(of: ".sqlite", with: "").lowercased()
let fullname = docsPath + "/" + shortname
   
do {
   print("Delete \(shortname)")
   try fileManager.removeItem(atPath: fullname)

   print("Create .sqlite file, initialize spatialite and connect to the database")
   let dbQueue = try DatabaseQueue(path: fullname)
   try dbQueue.inDatabase { db in
       spatialite_initialize()
       var spconnect: OpaquePointer?
       spatialite_init_ex(db.sqliteConnection, &spconnect, 1)
      
       print("Create \(tablename) table")
       try db.create(table: tablename, ifNotExists: true) { t in
           t.autoIncrementedPrimaryKey("ogc_fid")
           t.column("title", .text)
           t.column("descr", .text)
       }
       
       print("Create spatialite metadata structure")
       try db.execute(literal: "SELECT InitSpatialMetaData('WGS84');")
       
       print("Add POINT geometry field & create a spatial index for it")
       let sqlSpatial = [
               "SELECT AddGeometryColumn('\(tablename)', 'geom', 4326, 'POINT', 'XY');",
               "SELECT CreateSpatialIndex('\(tablename)', 'geom');"]
       for sql in sqlSpatial {
           try db.execute(sql: sql)
       }
       
       let insertStr = "INSERT INTO \(tablename) (title, descr, geom) " +
               "VALUES (?, ?, ST_GeomFromText('POINT(126.0 -34.0)', 4326) );"
       print(insertStr)
       try db.execute(sql: insertStr, arguments: ["Pelican", "Thermaling up high."])

       let sqlStr = "SELECT ogc_fid, title, descr, ST_AsText(geom) AS wkt FROM \(tablename);"
       print(sqlStr)
       let rows = try Row.fetchCursor(db, sql:sqlStr, arguments: [])
       while let row = try rows.next() {
           let id = row["ogc_fid"]
           let title = row["title"]
           let descr = row["descr"]
           let wkt = row["wkt"]
           print("ogc_fid=\(id!), title=\(title!), desc=\(descr!), geom=\(wkt!)")
       }
       
   }
} catch let error as NSError {
   let str = "\(error.debugDescription)"
   print(str)
   return
}
      

@ali711
Copy link

ali711 commented Mar 20, 2024

@smbkr @mrclayman i have seen both of you guys have worked on building spatialite i tried both of the solutions.
I built spatialite 4.4.0 using https://github.com/smbkr/libspatialite-ios/blob/master/Makefile and
Last i used was https://github.com/mrclayman/libspatialite-ios/blob/libspatialite-5/Makefile

In both of the solutions base spatialite functions works fine in IOS but functions which require geos or proj or rtree does not work.(i also built another with rttopo for test purpose by manually disabeling GEOSContext_setErrorMessageHandler_r() check and it was built successfully)
For example ST_IsValid , IsValidReason , ST_Within , ST_Overlaps etc. ST_Within does not filter any feature. ST_IsValid always returns -1 even when i tried it on SELECT ST_IsValid(ST_GeomFromText('POINT(15.785 21.713)')).
I believe there is some library communication problem or might need to update build process.
I have posted complete problem detail here as well
https://stackoverflow.com/questions/78187601/st-within-st-isvalid-or-any-other-geos-or-rtreetopo-related-function-not-working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment