/[thuban]/branches/WIP-pyshapelib-bramz/test/postgissupport.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/test/postgissupport.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1605 by bh, Tue Aug 19 11:00:40 2003 UTC revision 2589 by bh, Tue Mar 29 18:36:53 2005 UTC
# Line 1  Line 1 
1  # Copyright (C) 2003 by Intevation GmbH  # Copyright (C) 2003, 2004, 2005 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4  #  #
# Line 17  import time Line 17  import time
17  import popen2  import popen2
18  import shutil  import shutil
19  import traceback  import traceback
20    import re
21    
22  import support  import support
23    
# Line 115  class PostgreSQLServer: Line 116  class PostgreSQLServer:
116          self.socket_dir = socket_dir          self.socket_dir = socket_dir
117    
118          # For the client side the socket directory can be used as the          # For the client side the socket directory can be used as the
119          # host the name starts with a slash.          # host if the name starts with a slash.
120          self.host = os.path.abspath(socket_dir)          self.host = os.path.abspath(socket_dir)
121    
122            # name and password for the admin and an unprivileged user
123            self.admin_name = "postgres"
124            self.admin_password = "postgres"
125            self.user_name = "observer"
126            self.user_password = "telescope"
127    
128          # Map db names to db objects          # Map db names to db objects
129          self.known_dbs = {}          self.known_dbs = {}
130    
# Line 135  class PostgreSQLServer: Line 142  class PostgreSQLServer:
142              shutil.rmtree(self.dbdir)              shutil.rmtree(self.dbdir)
143          os.mkdir(self.dbdir)          os.mkdir(self.dbdir)
144    
145          run_command(["initdb", self.dbdir],          run_command(["initdb", "-D", self.dbdir, "-U", self.admin_name],
146                      os.path.join(self.dbdir, "initdb.log"))                      os.path.join(self.dbdir, "initdb.log"))
147    
148          extra_opts = "-p %d" % self.port          extra_opts = "-p %d" % self.port
# Line 150  class PostgreSQLServer: Line 157  class PostgreSQLServer:
157          # server ourselves          # server ourselves
158          self.wait_for_postmaster()          self.wait_for_postmaster()
159    
160            self.alter_user(self.admin_name, self.admin_password)
161            self.create_user(self.user_name, self.user_password)
162    
163      def wait_for_postmaster(self):      def wait_for_postmaster(self):
164          """Return when the database server is running          """Return when the database server is running
165    
# Line 161  class PostgreSQLServer: Line 171  class PostgreSQLServer:
171          while count < max_count:          while count < max_count:
172              try:              try:
173                  run_command(["psql", "-l", "-p", str(self.port),                  run_command(["psql", "-l", "-p", str(self.port),
174                               "-h", self.host],                               "-h", self.host, "-U", self.admin_name],
175                              os.path.join(self.dbdir, "psql-%d.log" % count))                              os.path.join(self.dbdir, "psql-%d.log" % count))
176              except:              except RuntimeError:
177                  pass                  pass
178                except:
179                    traceback.print_exc()
180              else:              else:
181                  break                  break
182              time.sleep(0.5)              time.sleep(0.5)
# Line 173  class PostgreSQLServer: Line 185  class PostgreSQLServer:
185              raise RuntimeError("postmaster didn't start")              raise RuntimeError("postmaster didn't start")
186    
187      def is_running(self):      def is_running(self):
188          """Return true a postmaster process is running on self.dbdir          """Return whether a postmaster process is running on self.dbdir
189    
190          This method runs pg_ctl status on the dbdir so even if the          This method runs pg_ctl status on the dbdir and returns True if
191          object has just been created it is possible that this method          that command succeeds and False otherwise.
192          returns true if there's still a postmaster process running for  
193          self.dbdir.          Note that it is possible that this method returns true even if
194            the PostgreSQLServer instance has just been created and
195            createdb() has not been called yet.  This can happen, for
196            instance, if the server has been started manually for debugging
197            purposes after a test suite run.
198          """          """
199          return run_boolean_command(["pg_ctl", "-D", self.dbdir, "status"])          return run_boolean_command(["pg_ctl", "-D", self.dbdir, "status"])
200    
# Line 187  class PostgreSQLServer: Line 203  class PostgreSQLServer:
203          run_command(["pg_ctl", "-m", "fast", "-D", self.dbdir, "stop"],          run_command(["pg_ctl", "-m", "fast", "-D", self.dbdir, "stop"],
204                      os.path.join(self.dbdir, "pg_ctl-stop.log"))                      os.path.join(self.dbdir, "pg_ctl-stop.log"))
205    
206      def new_postgis_db(self, dbname, tables = None):      def new_postgis_db(self, dbname, tables = None, reference_systems = None,
207                           views = None):
208          """Create and return a new PostGISDatabase object using self as server          """Create and return a new PostGISDatabase object using self as server
209          """          """
210          db = PostGISDatabase(self, self.postgis_sql, dbname, tables = tables)          db = PostGISDatabase(self, self.postgis_sql, dbname, tables = tables,
211                                 reference_systems = reference_systems,
212                                 views = views)
213          db.initdb()          db.initdb()
214          self.known_dbs[dbname] = db          self.known_dbs[dbname] = db
215          return db          return db
216    
217      def get_static_data_db(self, dbname, tables):      def get_static_data_db(self, dbname, tables, reference_systems, views):
218          """Return a PostGISDatabase for a database with the given static data          """Return a PostGISDatabase for a database with the given static data
219    
220          If no databasse of the name dbname exists, create a new one via          If no databasse of the name dbname exists, create a new one via
# Line 205  class PostgreSQLServer: Line 224  class PostgreSQLServer:
224          indicated data, return that. If the already existing db uses          indicated data, return that. If the already existing db uses
225          different data raise a value error.          different data raise a value error.
226    
227          The tables argument should be a sequence of table specifications          If the database doesn't exist, create a new one via
228          where each specifications is a (tablename, shapefilename) pair.          self.new_postgis_db.
229    
230            The parameters tables and reference_systems have the same
231            meaning as for new_postgis_db.
232          """          """
233          db = self.known_dbs.get(dbname)          db = self.known_dbs.get(dbname)
234          if db is not None:          if db is not None:
235              if db.has_data(tables):              if db.has_data(tables, reference_systems, views):
236                  return db                  return db
237              raise ValueError("PostGISDatabase named %r doesn't have tables %r"              raise ValueError("PostGISDatabase named %r doesn't have tables %r"
238                               % (dbname, tables))                               % (dbname, tables))
239          return self.new_postgis_db(dbname, tables)          return self.new_postgis_db(dbname, tables, reference_systems, views)
240    
241      def get_default_static_data_db(self):      def get_default_static_data_db(self):
242          dbname = "PostGISStaticTests"          dbname = "PostGISStaticTests"
243          tables = [("landmarks", os.path.join("..", "Data", "iceland",          srids = [(1, "proj=longlat datum=WGS84")]
244                                               "cultural_landmark-point.shp")),          tables = [
245                    ("political", os.path.join("..", "Data", "iceland",              # Direct copies of the shapefiles. The shapeids are exactly
246                # the same, except where changed with "gid_offset", of
247                # course.  Note that the test implementation requires that
248                # all the landmard tables use an gid_offset of 1000.
249                ("landmarks", os.path.join("..", "Data", "iceland",
250                                           "cultural_landmark-point.shp"),
251                 [("gid_offset", 1000)]),
252                ("political", os.path.join("..", "Data", "iceland",
253                                               "political.shp")),                                               "political.shp")),
254                    ("roads", os.path.join("..", "Data", "iceland",              ("roads_multi", os.path.join("..", "Data", "iceland",
255                                           "roads-line.shp"))]                                           "roads-line.shp")),
256          return self.get_static_data_db(dbname, tables)  
257                # same as roads-multi but using LINESTRING instead of
258                # MULTILINESTRING
259                ("roads", os.path.join("..", "Data", "iceland",
260                                             "roads-line.shp"),
261                 [("force_wkt_type", "LINESTRING")]),
262    
263                # The polygon data as a MULTIPOLYGON geometry type
264                ("political_multi", os.path.join("..", "Data", "iceland",
265                                                 "political.shp"),
266                 [("force_wkt_type", "MULTIPOLYGON")]),
267    
268                # Copy of landmarks but using an srid != -1
269                ("landmarks_srid", os.path.join("..", "Data", "iceland",
270                                           "cultural_landmark-point.shp"),
271                 [("gid_offset", 1000),
272                  ("srid", 1)]),
273    
274                # Copy of landmarks with a gid column called "point_id" instead
275                # of "gid" and using an srid != -1.
276                ("landmarks_point_id", os.path.join("..", "Data", "iceland",
277                                                    "cultural_landmark-point.shp"),
278                 [("gid_offset", 1000),
279                  ("srid", 1),
280                  ("gid_column", "point_id")]),
281                ]
282            views = [("v_landmarks", "SELECT * FROM landmarks_point_id")]
283            return self.get_static_data_db(dbname, tables, srids, views)
284    
285        def connection_params(self, user):
286            """Return the connection parameters for the given user
287    
288            The return value is a dictionary suitable as keyword argument
289            list to PostGISConnection. The user parameter may be either
290            'admin' to connect as admin or 'user' to connect as an
291            unprivileged user.
292            """
293            return {"host": self.host, "port": self.port,
294                    "user": getattr(self, user + "_name"),
295                    "password": getattr(self, user + "_password")}
296    
297        def connection_string(self, user):
298            """Return (part of) the connection string to pass to psycopg.connect
299    
300            The string contains host, port, user and password. The user
301            parameter must be either 'admin' or 'user', as for
302            connection_params.
303            """
304            params = []
305            for key, value in self.connection_params(user).items():
306                # FIXME: this doesn't do quiting correctly but that
307                # shouldn't be much of a problem (people shouldn't be using
308                # single quotes in filenames anyway :) )
309                params.append("%s='%s'" % (key, value))
310            return " ".join(params)
311    
312        def execute_sql(self, dbname, user, sql):
313            """Execute the sql statament and return a result for SELECT statements
314    
315            The user parameter us used as in connection_params. The dbname
316            parameter must be the name of a database in the cluster.  The
317            sql parameter is the SQL statement to execute as a string.  If
318            the string starts with 'select' (matched case insensitively) the
319            first row of the result will be returned.  Otherwise the return
320            value is None.
321            """
322            conn = psycopg.connect("dbname=%s " % dbname
323                                   + self.connection_string(user))
324            cursor = conn.cursor()
325            cursor.execute(sql)
326            if sql.lower().startswith("select"):
327                row = cursor.fetchone()
328            else:
329                row = None
330            conn.commit()
331            conn.close()
332            return row
333    
334        def server_version(self):
335            """Return the server version as a tuple (major, minor, patch)
336    
337            Each item in the tuple is an int.
338            """
339            result = self.execute_sql("template1", "admin", "SELECT version();")[0]
340            match = re.match(r"PostgreSQL (\d+\.\d+\.\d+)", result)
341            if match:
342                return tuple(map(int, match.group(1).split(".")))
343            else:
344                raise RutimeError("Cannot determine PostgreSQL server version"
345                                  " from %r" % result)
346    
347        def require_authentication(self, required):
348            """Switch authentication requirements on or off
349    
350            When started for the first time no passwords are required. Some
351            tests want to explicitly test whether Thuban's password
352            infrastructure works and switch password authentication on
353            explicitly. When switching it on, there should be a
354            corresponding call to switch it off again in the test case'
355            tearDown method or in a finally: block.
356            """
357            # Starting with PostgreSQL 7.3 the pg_hba.conf file has an
358            # additional column with a username.  Query the server version
359            # and generate a file in the correct format.
360            if self.server_version() >= (7, 3):
361                user = "all"
362            else:
363                user = ""
364            if required:
365                contents = "local all %s password\n" % user
366            else:
367                contents = "local all %s trust\n" % user
368            f = open(os.path.join(self.dbdir, "pg_hba.conf"), "w")
369            f.write(contents)
370            f.close()
371            run_command(["pg_ctl", "-D", self.dbdir, "reload"],
372                        os.path.join(self.dbdir, "pg_ctl-reload.log"))
373    
374    
375        def create_user(self, username, password):
376            """Create user username with password in the database"""
377            self.execute_sql("template1", "admin",
378                             "CREATE USER %s PASSWORD '%s';" % (username,password))
379    
380        def alter_user(self, username, password):
381            """Change the user username's password in the database"""
382            self.execute_sql("template1", "admin",
383                             "ALTER USER %s PASSWORD '%s';" % (username,password))
384    
385    
386  class PostGISDatabase:  class PostGISDatabase:
387    
388      """A PostGIS database in a PostgreSQLServer"""      """A PostGIS database in a PostgreSQLServer"""
389    
390      def __init__(self, server, postgis_sql, dbname, tables = None):      def __init__(self, server, postgis_sql, dbname, tables = None,
391                     reference_systems = (), views = None):
392            """Initialize the PostGISDatabase
393    
394            Parameters:
395    
396                server -- The PostgreSQLServer instance containing the
397                    database
398    
399                postgis_sql -- Filename of the sql file with the postgis
400                    initialization code
401    
402                dbname -- The name of the database
403    
404                tables -- Optional description of tables to create in the
405                    new database. If given it should be a list of
406                    (tablename, shapefilename) pairs meaning that a table
407                    tablename will be created with the contents of the given
408                    shapefile or (tablename, shapefilename, extraargs)
409                    triples. The extraargs should be a list of key, value
410                    pairs to use as keyword arguments to upload_shapefile.
411    
412                reference_systems -- Optional description of spatial
413                    reference systems.  If given, it should be a sequence of
414                    (srid, params) pairs where srid is the srid defined by
415                    the proj4 paramter string params.  The srid can be given
416                    as an extra parameter in the tables list.
417    
418                views -- Optional description of views.  If given it should
419                    be a list of (viewname, select_stmt) pairs where
420                    viewname is the name of the view to be created and
421                    select_stmt is the select statement to use as the basis.
422                    The views will be created after the tables and may refer
423                    to them in the select_stmt.
424            """
425          self.server = server          self.server = server
426          self.postgis_sql = postgis_sql          self.postgis_sql = postgis_sql
427          self.dbname = dbname          self.dbname = dbname
428          self.tables = tables          self.tables = tables
429            self.views = views
430            if reference_systems:
431                self.reference_systems = reference_systems
432            else:
433                # Make sure that it's a sequence we can iterate over even if
434                # the parameter's None
435                self.reference_systems = ()
436    
437      def initdb(self):      def initdb(self):
438          """Remove the old db directory and create and initialize a new database          """Remove the old db directory and create and initialize a new database
439          """          """
440          run_command(["createdb", "-p", str(self.server.port),          run_command(["createdb", "-p", str(self.server.port),
441                       "-h", self.server.host, self.dbname],                       "-h", self.server.host, "-U", self.server.admin_name,
442                         self.dbname],
443                      os.path.join(self.server.dbdir, "createdb.log"))                      os.path.join(self.server.dbdir, "createdb.log"))
444          run_command(["createlang", "-p", str(self.server.port),          run_command(["createlang", "-p", str(self.server.port),
445                       "-h", self.server.host, "plpgsql", self.dbname],                       "-h", self.server.host,  "-U", self.server.admin_name,
446                         "plpgsql", self.dbname],
447                      os.path.join(self.server.dbdir, "createlang.log"))                      os.path.join(self.server.dbdir, "createlang.log"))
448          # for some reason psql doesn't exit with an error code if the          # for some reason psql doesn't exit with an error code if the
449          # file given as -f doesn't exist, so we check manually by trying          # file given as -f doesn't exist, so we check manually by trying
# Line 255  class PostGISDatabase: Line 452  class PostGISDatabase:
452          f.close()          f.close()
453          del f          del f
454          run_command(["psql", "-f", self.postgis_sql, "-d", self.dbname,          run_command(["psql", "-f", self.postgis_sql, "-d", self.dbname,
455                       "-p", str(self.server.port), "-h", self.server.host],                       "-p", str(self.server.port), "-h", self.server.host,
456                         "-U", self.server.admin_name],
457                       os.path.join(self.server.dbdir, "psql.log"))                       os.path.join(self.server.dbdir, "psql.log"))
458    
459            self.server.execute_sql(self.dbname, "admin",
460                                    "GRANT SELECT ON geometry_columns TO PUBLIC;")
461            self.server.execute_sql(self.dbname, "admin",
462                                    "GRANT SELECT ON spatial_ref_sys TO PUBLIC;")
463    
464            for srid, params in self.reference_systems:
465                self.server.execute_sql(self.dbname, "admin",
466                                        "INSERT INTO spatial_ref_sys VALUES"
467                                        " (%d, '', %d, '', '%s');"
468                                        % (srid, srid, params))
469          if self.tables is not None:          if self.tables is not None:
470              for tablename, shapefile in self.tables:              def unpack(item):
471                  upload_shapefile(shapefile, self, tablename)                  extra = {"force_wkt_type": None, "gid_offset": 0,
472                             "srid": -1}
473      def has_data(self, tables):                  if len(info) == 2:
474          return self.tables == tables                      tablename, shapefile = info
475                    else:
476                        tablename, shapefile, kw = info
477                        for key, val in kw:
478                            extra[key] = val
479                    return tablename, shapefile, extra
480    
481                for info in self.tables:
482                    tablename, shapefile, kw = unpack(info)
483                    upload_shapefile(shapefile, self, tablename, **kw)
484    
485            if self.views is not None:
486                for viewname, select_stmt in self.views:
487                    self.server.execute_sql(self.dbname, "admin",
488                                            "CREATE VIEW %s AS %s" % (viewname,
489                                                                      select_stmt))
490                    self.server.execute_sql(self.dbname, "admin",
491                                            "GRANT SELECT ON %s TO PUBLIC;"
492                                            % viewname)
493    
494        def has_data(self, tables, reference_systems, views):
495            return (self.tables == tables
496                    and self.reference_systems == reference_systems
497                    and self.views == views)
498    
499    
500  def find_postgis_sql():  def find_postgis_sql():
501      """Return the name of the postgis_sql file      """Return the name of the postgis_sql file
502    
503      A postgis installation usually has the postgis_sql file in      A postgis installation usually has the postgis_sql file in
504      PostgreSQL's datadir (i.e. the directory where PostgreSQL keeps      PostgreSQL's $datadir (i.e. the directory where PostgreSQL keeps
505      static files, not the directory containing the databases).      static files, not the directory containing the databases).
506      Unfortunately there's no way to determine the name of this directory      Unfortunately there's no way to determine the name of this directory
507      with pg_config so we assume here that it's      with pg_config so we assume here that it's
508      $bindir/../share/postgresql/.      $bindir/../share/postgresql/.
509    
510        Furthermore, different versions of postgis place the file in
511        slightly different locations or may even use different names.  For
512        instance:
513    
514          postgis 0.7.5        $datadir/contrib/postgis.sql
515          postgis 0.8.1        $datadir/postgis.sql
516          postgis 1.0.0-rc1    $datadir/lwpostgis.sql
517          postgis 1.0.0-rc4    $datadir/contrib/lwpostgis.sql
518    
519        To support both versions, we look in both places and return the
520        first one found (looking under contrib first).  If the file is not
521        found the return value is None.
522      """      """
523      bindir = run_config_script("pg_config --bindir").strip()      bindir = run_config_script("pg_config --bindir").strip()
524      return os.path.join(bindir, "..", "share", "postgresql",      datadir = os.path.join(bindir, "..", "share", "postgresql")
525                          "contrib", "postgis.sql")      for filename in [os.path.join(datadir, "contrib", "postgis.sql"),
526                         os.path.join(datadir, "postgis.sql"),
527                         os.path.join(datadir, "lwpostgis.sql"),
528                         os.path.join(datadir, "contrib", "lwpostgis.sql")]:
529            if os.path.exists(filename):
530                return filename
531    
532    
533  _postgres_server = None  _postgres_server = None
534  def get_test_server():  def get_test_server():
# Line 322  def reason_for_not_running_tests(): Line 572  def reason_for_not_running_tests():
572         The name of the postgis_sql file is determined by find_postgis_sql()         The name of the postgis_sql file is determined by find_postgis_sql()
573       - psycopg can be imported successfully.       - psycopg can be imported successfully.
574      """      """
575        # run_command currently uses Popen4 which is not available under
576        # Windows, for example.
577        if not hasattr(popen2, "Popen4"):
578            return "Can't run PostGIS test because popen2.Popen4 does not exist"
579    
580      try:      try:
581          run_command(["pg_ctl", "--help"], None)          run_command(["pg_ctl", "--help"], None)
582      except RuntimeError:      except RuntimeError:
# Line 354  def skip_if_no_postgis(): Line 609  def skip_if_no_postgis():
609      if _cannot_run_postgis_tests:      if _cannot_run_postgis_tests:
610          raise support.SkipTest(_cannot_run_postgis_tests)          raise support.SkipTest(_cannot_run_postgis_tests)
611    
612  def point_to_wkt(coords):  def skip_if_addgeometrycolumn_does_not_use_quote_ident():
613        """Skip a test if the AddGeometryColumn function doesn't use quote_ident
614    
615        If the AddGeometryColumn function doesn't use quote_ident it doesn't
616        support unusual table or column names properly, that is, it will
617        fail with errors for names that contain spaces or double quotes.
618    
619        The test performed by this function is a bit simplistic because it
620        only tests whether the string 'quote_ident' occurs anywhere in the
621        postgis.sql file. This will hopefully work because when this was
622        fixed in postgis CVS AddGeometryColumn was the first function to use
623        quote_ident.
624        """
625        f = file(find_postgis_sql())
626        content = f.read()
627        f.close()
628        if content.find("quote_ident") < 0:
629            raise support.SkipTest("AddGeometryColumn doesn't use quote_ident")
630    
631    def coords_to_point(coords):
632      """Return string with a WKT representation of the point in coords"""      """Return string with a WKT representation of the point in coords"""
633      x, y = coords[0]      x, y = coords[0]
634      return "POINT(%r %r)" % (x, y)      return "POINT(%r %r)" % (x, y)
635    
636  def polygon_to_wkt(coords):  def coords_to_polygon(coords):
637      """Return string with a WKT representation of the polygon in coords"""      """Return string with a WKT representation of the polygon in coords"""
638      poly = []      poly = []
639      for ring in coords:      for ring in coords:
640          poly.append(", ".join(["%r %r" % p for p in ring]))          poly.append(", ".join(["%r %r" % p for p in ring]))
641      return "POLYGON((%s))" % "), (".join(poly)      return "POLYGON((%s))" % "), (".join(poly)
642    
643  def arc_to_wkt(coords):  def coords_to_linestring(coords):
644      """Return string with a WKT representation of the arc in coords"""      """Return string with a LINESTRING WKT representation of coords"""
645        if len(coords) > 1:
646            raise ValueError("A LINESTRING can only have one arc")
647        return "LINESTRING(%s)" % ", ".join(["%r %r" % p for p in coords[0]])
648    
649    def coords_to_multilinestring(coords):
650        """Return string with a MULTILINESTRING WKT representation of coords"""
651      poly = []      poly = []
652      for ring in coords:      for ring in coords:
653          poly.append(", ".join(["%r %r" % p for p in ring]))          poly.append(", ".join(["%r %r" % p for p in ring]))
654      return "MULTILINESTRING((%s))" % "), (".join(poly)      return "MULTILINESTRING((%s))" % "), (".join(poly)
655    
656  def upload_shapefile(filename, db, tablename):  def coords_to_multipolygon(coords):
657        """Return string with a WKT representation of the polygon in coords"""
658        poly = []
659        for ring in coords:
660            poly.append(", ".join(["%r %r" % p for p in ring]))
661        return "MULTIPOLYGON(((%s)))" % ")), ((".join(poly)
662    
663    wkt_converter = {
664        "POINT": coords_to_point,
665        "LINESTRING": coords_to_linestring,
666        "MULTILINESTRING": coords_to_multilinestring,
667        "POLYGON": coords_to_polygon,
668        "MULTIPOLYGON": coords_to_multipolygon,
669        }
670    
671    def upload_shapefile(filename, db, tablename, force_wkt_type = None,
672                         gid_offset = 0, gid_column = "gid", srid = -1):
673        """Upload a shapefile into a new database table
674    
675        Parameters:
676    
677        filename -- The name of the shapefile
678    
679        db -- The PostGISDatabase instance representing the database
680    
681        tablename -- The name of the table to create and into which the data
682                    is to be inserted
683    
684        force_wkt_type -- If given and not None, this is used as the WKT
685                    geometry type to use instead of the default that would
686                    be chosen based on the type of the shapefile
687    
688        gid_offset -- A number to add to the shapeid to get the value for
689                    the gid column (default 0)
690    
691        gid_column -- The name of the column with the shape ids.  Default
692                      'gid'.  If None, no gid column will be created.  The
693                      name is directly used in SQL statements, so if it
694                      contains unusualy characters the caller should provide
695                      a suitable quoted string.
696    
697        srid -- The srid of the spatial references system used by the table
698                and the data
699        """
700      import dbflib, shapelib      import dbflib, shapelib
701    
702        # We build this map here because we need shapelib which can only be
703        # imported after support.initthuban has been called which we can't
704        # easily do in this module because it's imported by support.
705        shp_to_wkt = {
706            shapelib.SHPT_POINT: "POINT",
707            shapelib.SHPT_ARC: "MULTILINESTRING",
708            shapelib.SHPT_POLYGON: "POLYGON",
709            }
710    
711      server = db.server      server = db.server
712      dbname = db.dbname      dbname = db.dbname
713      conn = psycopg.connect("host=%s port=%s dbname=%s"      conn = psycopg.connect("dbname=%s " % dbname
714                             % (server.host, server.port, dbname))                             + db.server.connection_string("admin"))
715      cursor = conn.cursor()      cursor = conn.cursor()
716    
717      shp = shapelib.ShapeFile(filename)      shp = shapelib.ShapeFile(filename)
# Line 388  def upload_shapefile(filename, db, table Line 720  def upload_shapefile(filename, db, table
720                 dbflib.FTInteger: "INTEGER",                 dbflib.FTInteger: "INTEGER",
721                 dbflib.FTDouble: "DOUBLE PRECISION"}                 dbflib.FTDouble: "DOUBLE PRECISION"}
722    
723      insert_formats = ["%(gid)s"]      insert_formats = []
724      fields = ["gid INT"]      if gid_column:
725            insert_formats.append("%(gid)s")
726    
727        fields = []
728        fields_decl = []
729        if gid_column:
730            fields.append(gid_column)
731            fields_decl.append("%s INT" % gid_column)
732      for i in range(dbf.field_count()):      for i in range(dbf.field_count()):
733          ftype, name, width, prec = dbf.field_info(i)          ftype, name, width, prec = dbf.field_info(i)
734          fields.append("%s %s" % (name, typemap[ftype]))          fields.append(name)
735            fields_decl.append("%s %s" % (name, typemap[ftype]))
736          insert_formats.append("%%(%s)s" % name)          insert_formats.append("%%(%s)s" % name)
737      stmt = "CREATE TABLE %s (\n    %s\n);" % (tablename,      stmt = "CREATE TABLE %s (\n    %s\n);" % (tablename,
738                                                ",\n    ".join(fields))                                                ",\n    ".join(fields_decl))
739      cursor.execute(stmt)      cursor.execute(stmt)
740      #print stmt      #print stmt
741    
742      numshapes, shapetype, mins, maxs = shp.info()      numshapes, shapetype, mins, maxs = shp.info()
743      if shapetype == shapelib.SHPT_POINT:      wkttype =  shp_to_wkt[shapetype]
744          convert = point_to_wkt      if force_wkt_type:
745          wkttype = "POINT"          wkttype = force_wkt_type
746      elif shapetype == shapelib.SHPT_POLYGON:      convert = wkt_converter[wkttype]
         convert = polygon_to_wkt  
         wkttype = "POLYGON"  
     elif shapetype == shapelib.SHPT_ARC:  
         convert = arc_to_wkt  
         wkttype = "MULTILINESTRING"  
     else:  
         raise ValueError("Unsupported Shapetype %r" % shapetype)  
747    
748      cursor.execute("select AddGeometryColumn('%(dbname)s',"      cursor.execute("select AddGeometryColumn('%(dbname)s',"
749                     "'%(tablename)s', 'the_geom', '-1', '%(wkttype)s', 2);"                     "'%(tablename)s', 'the_geom', %(srid)d, '%(wkttype)s', 2);"
750                     % locals())                     % locals())
751        fields.append("the_geom")
752        insert_formats.append("GeometryFromText(%(the_geom)s, %(srid)d)")
753    
754      insert_formats.append("GeometryFromText(%(the_geom)s, -1)")      insert = ("INSERT INTO %s (%s) VALUES (%s)"
755                  % (tablename, ", ".join(fields), ", ".join(insert_formats)))
     insert = ("INSERT INTO %s VALUES (%s)"  
               % (tablename, ", ".join(insert_formats)))  
756    
757      for i in range(numshapes):      for i in range(numshapes):
758          data = dbf.read_record(i)          data = dbf.read_record(i)
759          data["tablename"] = tablename          data["tablename"] = tablename
760          data["gid"] = i          if gid_column:
761                data["gid"] = i + gid_offset
762            data["srid"] = srid
763          data["the_geom"] = convert(shp.read_object(i).vertices())          data["the_geom"] = convert(shp.read_object(i).vertices())
764          #print insert % data          #print insert % data
765          cursor.execute(insert, data)          cursor.execute(insert, data)
766    
767        cursor.execute("GRANT SELECT ON %s TO PUBLIC;" % tablename)
768    
769      conn.commit()      conn.commit()

Legend:
Removed from v.1605  
changed lines
  Added in v.2589

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26