コード例 #1
0
def create_point_col_from_lat_lon(new_table_owner, table_name, lat_column,
                                  lng_column):
    """
    Using a new DataTable and specified lat/lng column names, map the points

    :param new_table_owner:
    :param table_name:
    :param lat_column:
    :param lng_column:
    :return:
    """

    LOGGER.info('create_point_col_from_lat_lon')
    assert isinstance(new_table_owner,
                      User), "new_table_owner must be a User object"
    assert table_name is not None, "table_name cannot be None"
    assert lat_column is not None, "lat_column cannot be None"
    assert lng_column is not None, "lng_column cannot be None"

    # ----------------------------------------------------
    # Retrieve the DataTable and check for lat/lng columns
    # ----------------------------------------------------
    try:
        dt = DataTable.objects.get(table_name=table_name)
    except DataTable.DoesNotExist:
        err_msg = "Could not find DataTable with name: %s" % (table_name)
        LOGGER.error(err_msg)
        return False, err_msg

    # ----------------------------------------------------
    # Latitude attribute
    # ----------------------------------------------------
    lat_col_attr = dt.get_attribute_by_name(
        standardize_column_name(lat_column))
    if lat_col_attr is None:
        err_msg = 'DataTable "%s" does not have a latitude column named "%s" (formatted: %s)'\
                  % (table_name, lat_column, standardize_column_name(lat_column))
        LOGGER.error(err_msg)
        return False, err_msg

    is_valid, err_msg = is_valid_lat_lng_attribute(lat_col_attr)
    if not is_valid:
        LOGGER.error(err_msg)
        return False, err_msg

    # ----------------------------------------------------
    # Longitude attribute
    # ----------------------------------------------------
    lng_col_attr = dt.get_attribute_by_name(
        standardize_column_name(lng_column))
    if lng_col_attr is None:
        err_msg = 'DataTable "%s" does not have a longitude column named "%s" (formatted: %s)'\
                  % (table_name, lng_column, standardize_column_name(lng_column))
        LOGGER.error(err_msg)
        return False, err_msg

    is_valid, err_msg = is_valid_lat_lng_attribute(lng_col_attr,
                                                   lng_check=True)
    if not is_valid:
        LOGGER.error(err_msg)
        return False, err_msg

    # ----------------------------------------------------
    # Start mapping record
    # ----------------------------------------------------
    lat_lnt_map_record = LatLngTableMappingRecord(datatable=dt\
                                , lat_attribute=lat_col_attr\
                                , lng_attribute=lng_col_attr\
                                )

    msg('create_point_col_from_lat_lon - 2')

    # ----------------------------------------------------
    # Yank bad columns out of the DataTable
    # ----------------------------------------------------
    # See https://github.com/IQSS/dataverse/issues/2949

    (success,
     row_cnt_or_err_msg) = remove_bad_lat_lng_numbers(lat_lnt_map_record)
    if not success:
        if lat_lnt_map_record.id:
            lat_lnt_map_record.delete()
        return False, 'Failed to remove bad lat/lng values.'

    # The bad rows were not mapped
    lat_lnt_map_record.unmapped_record_count = row_cnt_or_err_msg

    # ---------------------------------------------
    # Format SQL to:
    #   (a) Add the Geometry column to the Datatable
    #   (b) Populate the column using the lat/lng attributes
    #   (c) Create column index
    # ---------------------------------------------
    # (a) Add column SQL
    alter_table_sql = "ALTER TABLE %s ADD COLUMN geom geometry(POINT,4326);" % (
        table_name)  # postgi 2.x

    # (b) Populate column SQL
    update_table_sql = "UPDATE %s SET geom = ST_SetSRID(ST_MakePoint(%s,%s),4326);" \
                    % (table_name, lng_col_attr.attribute, lat_col_attr.attribute)
    #update_table_sql = "UPDATE %s SET geom = ST_SetSRID(ST_MakePoint(cast(%s AS float), cast(%s as float)),4326);" % (table_name, lng_column, lat_column)

    # (c) Index column SQL
    create_index_sql = "CREATE INDEX idx_%s_geom ON %s USING GIST(geom);" % (
        table_name, table_name)

    # ---------------------------------------------
    # Run the SQL
    # ---------------------------------------------
    try:
        conn = psycopg2.connect(
            get_datastore_connection_string(is_dataverse_db=False))

        cur = conn.cursor()

        LOGGER.debug('Run alter table SQL: %s', alter_table_sql)
        cur.execute(alter_table_sql)

        LOGGER.debug('Run update table SQL: %s', update_table_sql)
        cur.execute(update_table_sql)

        LOGGER.debug('Run create index SQL: %s', create_index_sql)
        cur.execute(create_index_sql)

        conn.commit()
        conn.close()
    except Exception as ex_obj:
        conn.close()
        err_msg = "Error Creating Point Column from Latitude and Longitude %s" % (
            ex_obj)
        LOGGER.error(err_msg)
        return False, err_msg

    msg('create_point_col_from_lat_lon - 4')

    # ------------------------------------------------------
    # Create the Layer in GeoServer from the table
    # ------------------------------------------------------
    try:
        cat = Catalog(settings.GEOSERVER_BASE_URL + "rest",\
                    settings.GEOSERVER_CREDENTIALS[0],\
                    settings.GEOSERVER_CREDENTIALS[1])
        #      "admin", "geoserver")
        workspace = cat.get_workspace("geonode")
        ds_list = cat.get_xml(workspace.datastore_url)
        datastores = [
            datastore_from_index(cat, workspace, n)
            for n in ds_list.findall("dataStore")
        ]
        #----------------------------
        # Find the datastore
        #----------------------------
        ds = None
        from geonode.maps.utils import get_db_store_name
        for datastore in datastores:
            if datastore.name == get_db_store_name():
                ds = datastore

        if ds is None:
            err_msg = "Datastore not found: '%s' (lat/lng)" % (
                settings.DB_DATASTORE_NAME)
            return False, err_msg
        ft = cat.publish_featuretype(table_name,
                                     ds,
                                     "EPSG:4326",
                                     srs="EPSG:4326")
        cat.save(ft)
    except Exception as e:
        lat_lnt_map_record.delete()
        traceback.print_exc(sys.exc_info())
        err_msg = "Error creating GeoServer layer for %s: %s" % (table_name,
                                                                 str(e))
        return False, err_msg

    msg('create_point_col_from_lat_lon - 5 - add style')

    # ------------------------------------------------------
    # Set the Layer's default Style
    # ------------------------------------------------------
    set_default_style_for_latlng_layer(cat, ft)

    # ------------------------------------------------------
    # Create the Layer in GeoNode from the GeoServer Layer
    # ------------------------------------------------------
    try:
        layer, created = Layer.objects.get_or_create(
            name=table_name,
            defaults={
                "workspace":
                workspace.name,
                "store":
                ds.name,
                "storeType":
                ds.resource_type,
                "typename":
                "%s:%s" %
                (workspace.name.encode('utf-8'), ft.name.encode('utf-8')),
                "title":
                dt.title or 'No title provided',
                #"name" : dt.title or 'No title provided',
                "abstract":
                dt.abstract or 'No abstract provided',
                "uuid":
                str(uuid.uuid4()),
                "owner":
                new_table_owner,
                #"bbox_x0": Decimal(ft.latlon_bbox[0]),
                #"bbox_x1": Decimal(ft.latlon_bbox[1]),
                #"bbox_y0": Decimal(ft.latlon_bbox[2]),
                #"bbox_y1": Decimal(ft.latlon_bbox[3])
            })
        #set_attributes(layer, overwrite=True)
    except Exception as e:
        traceback.print_exc(sys.exc_info())
        err_msg = "Error creating GeoNode layer for %s: %s" % (table_name,
                                                               str(e))
        return False, err_msg

    # ----------------------------------
    # Set default permissions (public)
    # ----------------------------------
    layer.set_default_permissions()

    # ------------------------------------------------------------------
    # Create LayerAttributes for the new Layer (not done in GeoNode 2.x)
    # ------------------------------------------------------------------
    (attributes_created,
     err_msg) = create_layer_attributes_from_datatable(dt, layer)
    if not attributes_created:
        LOGGER.error(err_msg)
        layer.delete()  # Delete the layer
        return False, "Sorry there was an error creating the Datatable. (s:ll)"

    # ----------------------------------
    # Save a related LatLngTableMappingRecord
    # ----------------------------------
    lat_lnt_map_record.layer = layer

    # ----------------------------------
    # Retrieve matche/unmatched counts
    # ----------------------------------
    # Get datatable feature count - total record to map
    (success_datatable,
     datatable_feature_count) = get_layer_feature_count(dt.table_name)

    # Get layer feature count - mapped records
    (success_layer, layer_feature_count) = get_layer_feature_count(layer.name)

    # Set Record counts
    if success_layer and success_datatable:
        lat_lnt_map_record.mapped_record_count = layer_feature_count
        new_misssed_records = datatable_feature_count - layer_feature_count
        if lat_lnt_map_record.unmapped_record_count and lat_lnt_map_record.unmapped_record_count > 0:
            lat_lnt_map_record.unmapped_record_count += new_misssed_records
        else:
            lat_lnt_map_record.unmapped_record_count = new_misssed_records

    else:
        LOGGER.error('Failed to calculate Lat/Lng record counts')

    lat_lnt_map_record.save()

    return True, lat_lnt_map_record
コード例 #2
0
def setup_join(new_table_owner, table_name, layer_typename,
               table_attribute_name, layer_attribute_name):
    LOGGER.info('setup_join')
    """
    Setup the Table Join in GeoNode
    """

    assert isinstance(new_table_owner,
                      User), "new_table_owner must be a User object"
    assert table_name is not None, "table_name cannot be None"
    assert layer_typename is not None, "layer_typename cannot be None"
    assert table_attribute_name is not None, "table_attribute_name cannot be None"
    assert layer_attribute_name is not None, "layer_attribute_name cannot be None"

    LOGGER.info('setup_join. Step (1): Retrieve the DataTable object')
    try:
        dt = DataTable.objects.get(table_name=table_name)
    except DataTable.DoesNotExist:
        err_msg = 'No DataTable object found for table_name "%s"' % table_name
        LOGGER.error(err_msg)
        return None, err_msg

    LOGGER.info('setup_join. Step (2): Retrieve the Layer object')

    try:
        layer = Layer.objects.get(typename=layer_typename)
    except Layer.DoesNotExist:
        err_msg = 'No Layer object found for layer_typename "%s"' % layer_typename
        LOGGER.error(err_msg)
        return None, err_msg

    LOGGER.info('setup_join. Step (3): Retrieve the DataTableAttribute object')
    try:
        table_attribute = DataTableAttribute.objects.get(\
                                        datatable=dt,
                                        attribute=table_attribute_name)
    except DataTableAttribute.DoesNotExist:
        err_msg = 'No DataTableAttribute object found for table/attribute (%s/%s)' \
                  % (dt,
                     table_attribute_name)
        LOGGER.error(err_msg)
        return None, err_msg

    LOGGER.info('setup_join. Step (4): Retrieve the LayerAttribute object')
    try:
        layer_attribute = LayerAttribute.objects.get(\
                                layer=layer,
                                attribute=layer_attribute_name)
    except LayerAttribute.DoesNotExist:
        err_msg = 'No LayerAttribute object found for layer/attribute (%s/%s)'\
                  % (layer, layer_attribute_name)
        LOGGER.error(err_msg)
        return None, err_msg

    LOGGER.info('setup_join. Step (5): Build SQL statement to create view')

    layer_name = layer.typename.split(':')[1]

    # ------------------------------------------------------------------
    # (5) Check if the layer and the table are in the same store (database)
    # ------------------------------------------------------------------
    if layer.store != dt.tablespace:
        err_msg = 'layer (%s) and tablespace (%s) must be in the same database.'\
            % (layer.store, dt.tablespace)
        LOGGER.error(err_msg)
        return None, err_msg

    # ------------------------------------------------------------------
    # (5a) Check if the join columns compatible
    # ------------------------------------------------------------------
    column_checker = ColumnChecker(\
                                layer_name,
                                layer_attribute.attribute,
                                dt.table_name,
                                table_attribute.attribute)
    (are_cols_compatible,
     err_msg) = column_checker.are_join_columns_compatible()
    if not are_cols_compatible:  # Doesn't look good, return an error message
        return None, err_msg

    # ------------------------------------------------------------------
    # (5b) Create SQL statement for the tablejoin
    # ------------------------------------------------------------------
    view_name = get_unique_viewname(dt.table_name, layer_name)

    # SQL to create the view
    view_sql = ('CREATE VIEW {0}'
                ' AS SELECT {1}.{2}, {3}.*'
                ' FROM {1} INNER JOIN {3}'
                ' ON {1}."{4}" = {3}."{5}";'.format(\
                    view_name,  # 0
                    layer_name, # 1
                    THE_GEOM_LAYER_COLUMN, # 2
                    dt.table_name,  # 3
                    layer_attribute.attribute, # 4
                    table_attribute.attribute)) # 5

    # Materialized view for next version of Postgres
    """view_sql = ('create materialized view {0} as'
                   ' select {1}.the_geom, {2}.*'
                   ' from {1} inner join {2}'
                   ' on {1}."{3}" = {2}."{4}";').format(\
                       view_name,
                       layer_name,
                       dt.table_name,
                       layer_attribute.attribute,
                       table_attribute.attribute)
    """
    LOGGER.info('setup_join. Step (6): Retrieve stats')

    # ------------------------------------------------------------------
    # Retrieve stats
    # ------------------------------------------------------------------
    matched_count_sql = ('select count({0}) from {1} where {1}.{0}'
                         ' in (select "{2}" from {3});').format(\
                            table_attribute.attribute,
                            dt.table_name,
                            layer_attribute.attribute,
                            layer_name)

    unmatched_count_sql = ('select count({0}) from {1} where {1}.{0}'
                           ' not in (select "{2}" from {3});').format(\
                            table_attribute.attribute,
                            dt.table_name,
                            layer_attribute.attribute,
                            layer_name)

    unmatched_list_sql = ('select {0} from {1} where {1}.{0}'
                          ' not in (select "{2}" from {3}) limit 500;').format(\
                            table_attribute.attribute,
                            dt.table_name,
                            layer_attribute.attribute,
                            layer_name)

    # ------------------------------------------------------------------
    # Create a TableJoin object
    # ------------------------------------------------------------------
    LOGGER.info('setup_join. Step (7): Create a TableJoin object')
    tj, created = TableJoin.objects.get_or_create(\
                                source_layer=layer,
                                datatable=dt,
                                table_attribute=table_attribute,
                                layer_attribute=layer_attribute,
                                view_name=view_name,
                                view_sql=view_sql)
    tj.save()
    msgt('table join created! :) %s' % tj.id)

    # ------------------------------------------------------------------
    # Create the View (and double view)
    # ------------------------------------------------------------------
    LOGGER.info('setup_join. Step (8): Create the View (and double view)')

    # Convenience method to drop a view
    #
    drop_view_by_name(view_name)

    try:
        conn = psycopg2.connect(get_datastore_connection_string())
        cur = conn.cursor()

        # Create the new view
        #
        msg('view_sql: %s' % view_sql)
        cur.execute(view_sql)
        #cur.execute(double_view_sql)   # For later version of postgres

        # Record the counts for matched records and
        # add unmatched records to the TableJoin object

        # Unmatched count
        cur.execute(matched_count_sql)
        tj.matched_records_count = cur.fetchone()[0]

        # Matched count
        cur.execute(unmatched_count_sql)
        tj.unmatched_records_count = int(cur.fetchone()[0])

        # Unmatched records list
        if tj.unmatched_records_count > 0:
            cur.execute(unmatched_list_sql)
            tj.unmatched_records_list = ",".join(
                ['%s' % r[0] for r in cur.fetchall()])

        conn.commit()
        cur.close()

        # If no records match, then delete the TableJoin
        #
        if tj.matched_records_count == 0:
            # Delete the table join
            tj.delete()

            # Create an error message, log it, and send it back
            err_msg = ('No records matched.  Make sure that you chose'
                       ' the correct column and that the chosen layer'
                       ' is in the same geographic area.')
            LOGGER.error(err_msg)
            return None, err_msg

    except Exception as ex_obj:
        tj.delete()  # If needed for debugging, don't delete the table join

        traceback.print_exc(sys.exc_info())
        err_msg = "Error Joining table %s to layer %s: %s" % (\
                        table_name,
                        layer_typename,
                        str(ex_obj[0]))
        LOGGER.error(err_msg)

        if err_msg.find('You might need to add explicit type casts.') > -1:
            user_msg = "The chosen column is a different data type than the one expected."
        else:
            user_msg = err_msg

        return None, user_msg

    finally:
        conn.close()

    #--------------------------------------------------
    # Create the Layer in GeoServer from the view
    #--------------------------------------------------
    LOGGER.info(
        'setup_join. Step (9): Create the Layer in GeoServer from the view')
    try:
        LOGGER.info('setup_join. Step (9a): Find the datastore')
        #----------------------------
        # Find the datastore
        #----------------------------
        cat = Catalog(
            settings.GEOSERVER_BASE_URL + "rest",
            settings.GEOSERVER_CREDENTIALS[0],
            settings.GEOSERVER_CREDENTIALS[1])  # "admin", "geoserver")

        workspace = cat.get_workspace('geonode')
        ds_list = cat.get_xml(workspace.datastore_url)
        datastores = [
            datastore_from_index(cat, workspace, n)
            for n in ds_list.findall("dataStore")
        ]
        ds = None

        # Iterate through datastores
        #
        for datastore in datastores:
            #msg ('datastore name:', datastore.name)
            if datastore.name == layer.store:
                ds = datastore

        if ds is None:
            tj.delete()
            err_msg = "Datastore name not found: '%s' (join)" % settings.DB_DATASTORE_NAME
            LOGGER.error(str(ds))
            return None, err_msg

        # Publish the feature
        #
        LOGGER.info('setup_join. Step (9b): Publish the feature type')
        ft = cat.publish_featuretype(view_name, ds, layer.srs, srs=layer.srs)
        #ft = cat.publish_featuretype(double_view_name, ds, layer.srs, srs=layer.srs)

        LOGGER.info('setup_join. Step (9c): Save the feature type')
        cat.save(ft)

    except Exception as e:
        tj.delete()
        traceback.print_exc(sys.exc_info())
        err_msg = "Error creating GeoServer layer for %s: %s" % (view_name,
                                                                 str(e))
        LOGGER.error(err_msg)
        return None, err_msg

    # ------------------------------------------------------
    # Set the Layer's default Style
    # ------------------------------------------------------
    sld_success, err_msg = set_style_for_new_join_layer(cat, ft, layer)
    if not sld_success:
        return None, err_msg

    # ------------------------------------------------------------------
    # Create the Layer in GeoNode from the GeoServer Layer
    # ------------------------------------------------------------------
    LOGGER.info(
        'setup_join. Step (10): Create the Layer in GeoNode from the GeoServer Layer'
    )
    try:
        layer_params = {
            "workspace":
            workspace.name,
            "store":
            ds.name,
            "storeType":
            ds.resource_type,
            "typename":
            "%s:%s" %
            (workspace.name.encode('utf-8'), ft.name.encode('utf-8')),
            "title":
            dt.title or 'No title provided',
            "abstract":
            dt.abstract or 'No abstract provided',
            "uuid":
            str(uuid.uuid4()),
            "owner":
            new_table_owner,
            #"bbox_x0": Decimal(ft.latlon_bbox[0]),
            #"bbox_x1": Decimal(ft.latlon_bbox[1]),
            #"bbox_y0": Decimal(ft.latlon_bbox[2]),
            #"bbox_y1": Decimal(ft.latlon_bbox[3])
        }

        layer, created = Layer.objects.get_or_create(name=view_name,
                                                     defaults=layer_params)

        # Set default permissions (public)
        layer.set_default_permissions()
        #set_attributes(layer, overwrite=True)

        tj.join_layer = layer
        tj.save()
    except Exception as e:
        tj.delete()
        traceback.print_exc(sys.exc_info())
        err_msg = "Error creating GeoNode layer for %s: %s" % (view_name,
                                                               str(e))
        LOGGER.error(err_msg)
        return None, err_msg

    # ------------------------------------------------------------------
    # Create LayerAttributes for the new Layer (not done in GeoNode 2.x)
    # ------------------------------------------------------------------
    LOGGER.info(
        'setup_join. Step (11): Create Layer Attributes from the Datatable')
    (attributes_created,
     err_msg) = create_layer_attributes_from_datatable(dt, layer)
    if not attributes_created:
        LOGGER.error(err_msg)
        tj.delete()  # Delete the table join object
        return None, "Sorry there was an error creating the Datatable (s11)"

    return tj, ""