2012년 1월 31일 화요일

[PostGIS]ERROR: LWGEOM_estimated_extent

GeoServer 등 GeoTools를 사용하는 프로그램의 경우 PostGIS Data Store를 사용하여 레이어의Extent를 구할 때 다음과 같은 오류가 발생하는 경우가 있다.

▣ GeoTools - PostGIS
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put(JDBCDataStoreFactory.DBTYPE.key, "postgis");
params.put(JDBCDataStoreFactory.HOST.key, "localhost");
params.put(JDBCDataStoreFactory.PORT.key, 5432);
params.put(JDBCDataStoreFactory.SCHEMA.key, "public");
params.put(JDBCDataStoreFactory.DATABASE.key, "uoc");
params.put(JDBCDataStoreFactory.USER.key, "postgres");
params.put(JDBCDataStoreFactory.PASSWD.key, "postgis");

try {
    DataStore pgDataStore = DataStoreFinder.getDataStore(params);

    SimpleFeatureSource srcSfs = pgDataStore.getFeatureSource("ubsm0103");
    ReferencedEnvelope extent = srcSfs.getFeatures(Filter.INCLUDE).getBounds();

    System.out.println(extent);
} catch (IOException e) {
    LOGGER.log(Level.FINE, e.getMessage(), e);
}
▣ 오류
Warning: Failed to use ST_Estimated_Extent, falling back on envelope aggregation
org.postgresql.util.PSQLException: ERROR: LWGEOM_estimated_extent: couldn't locate table within current schema
    at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2062)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1795)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:479)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:353)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:252)
    at org.apache.commons.dbcp.DelegatingStatement.executeQuery(DelegatingStatement.java:208)
    at org.apache.commons.dbcp.DelegatingStatement.executeQuery(DelegatingStatement.java:208)
    at org.geotools.data.postgis.PostGISDialect.getOptimizedBounds(PostGISDialect.java:230)
    at org.geotools.jdbc.JDBCDataStore.getBounds(JDBCDataStore.java:1092)
    at org.geotools.jdbc.JDBCFeatureSource.getBoundsInternal(JDBCFeatureSource.java:478)
    at org.geotools.jdbc.JDBCFeatureStore.getBoundsInternal(JDBCFeatureStore.java:179)
    at org.geotools.data.store.ContentFeatureSource.getBounds(ContentFeatureSource.java:370)
    at org.geotools.data.store.ContentFeatureCollection.getBounds(ContentFeatureCollection.java:274)
▣ 오류원인
PostGISDialect.java 소스의 getOptimizedBounds 메쏘드를 찾아보면 다음과 같은 SQL문을 확인할 수 있다.
select AsText(force_2d(Envelope(ST_Estimated_Extent('ubsm0103', 'the_geom'))))
또는
SELECT ST_AsText(ST_Force_2D(ST_Envelope(ST_Estimated_Extent('ubsm0103', 'the_geom'))))
위 SQL에서 ST_Estimated_Extent 함수는 PostgreSQL 8.0.0 버전 이상에서는 VACUUM ANALYZE, PostgreSQL 8.0.0 이전 버전에서는 update_geometry_stats() 을 먼저 수행 해야 한다.

ST_Extent 함수를 사용하지 않은 이유는 속도때문입니다.

▣ 해결
PostGIS 초기 데이터 로딩 후 다음을 수행한다.
VACUUM ANALYZE
▣ 참고

 - http://docs.geotools.org/stable/javadocs/org/geotools/data/postgis/PostGISDialect.html
 - http://www.postgis.org/docs/ST_Estimated_Extent.html
 - http://www.postgresql.org/docs/9.0/interactive/sql-vacuum.html

2011년 12월 25일 일요일

[ArcObjects] Layer와 Feature Class/Table의 필드 별칭 설정

▣ 미션
 - ArcMap에서 아래 그림과 같이 테이블과 속성정보 조회시 영문필드에 한글필드로 별칭을 보여주고 싶다.
- 면적 등과 같이 Numeric 필드일 경우 소숫점 2째자리까지만 보여주고 천단위 구분자를 두고싶다.
- GeoDatabase(Personal, File, ArcSDE)에서 레이어를 불러올 경우 한글별칭을 기본값으로 사용하고 싶다.

▣ 설명
 - Layer의 필드정보는 Feature Layer 수준, FeatureClass, Table의 필드정보는 GeoDatabase 수준에서 변경.
 - 따라서 FeatureClass, Table의 필드정보 변경은 물리적으로 저장되고 Layer의 필드정보는 ArcMap과 같이 Application 내에서만 임시 적용됨.
 - FeatureClass에 이미 한글별칭이 적용되어 있더라도 Application에서 실시간 조인이 이루어지는 경우는 별칭이 적용되지 않을 수 있음.

▣ ArcObjects Interface
 - INumberFormat
 - ITableFields
 - IFieldInfo
 - IClassSchemaEdit
 - ISchemaLock

▣ Code Snippet
using System.Runtime.InteropServices;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Carto;
■ 필드이름과 매칭되는 Alias 정보 생성
System.Collections.Hashtable FieldAliasTable = new System.Collections.Hashtable();

public void AddFieldAlias() {
    FieldAliasTable.Add("OBJECTID", "일련번호");
    FieldAliasTable.Add("SHAPE", "공간정보");
    FieldAliasTable.Add("SHAPE_AREA", "면적");
    FieldAliasTable.Add("SHAPE_LENGTH", "둘레");
    
    // 기타 필드 추가
    FieldAliasTable.Add("NAM", "명칭");
}
■ 레이어의 필드 정보 변경
- 다음 코드는 위 Alias 매칭 테이블에 담긴 정보를 이용하여 면적 등과 같이 Numeric 필드일 경우 소숫점 2째자리까지만 보여주고 천단위 구분자 적용
- 테이블과 조인된 필드일 경우에도 한글 별칭을 일괄적으로 적용하는 예임
- 또한 필드 이름 중 NAM 이라는 String 필드가 포함되어 있을 경우 Identify 등에서 사용되는 Primary Display Field로 설정함
public void AlterLayerFieldAlias(IFeatureLayer featureLayer) {
    // Number format for Double, Single Fields
    INumericFormat numberFormat = new NumericFormatClass();
    numberFormat.RoundingOption = esriRoundingOptionEnum.esriRoundNumberOfDecimals;
    numberFormat.RoundingValue = 2;    // Number of decimal places
    numberFormat.ZeroPad = true;      // Pad with zeros
    numberFormat.UseSeparator = true; // Show thousands separators

    IGeoFeatureLayer geoFeatureLayer = (IGeoFeatureLayer)featureLayer;
    ITableFields tableFields = featureLayer as ITableFields;
    for (int fieldId = 0; fieldId < tableFields.FieldCount; fieldId++) {
        IFieldInfo fieldInfo = tableFields.get_FieldInfo(fieldId);
        IField field = tableFields.get_Field(fieldId);

        string fieldName = field.Name.ToUpper();
        string fieldAlias = fieldInfo.Alias;

        if (fieldName.Contains(".")) {
            int pos = fieldName.LastIndexOf(".");
            fieldName = fieldName.Substring(pos + 1).Trim();
        }

        switch (field.Type) {
            case esriFieldType.esriFieldTypeDouble:
            case esriFieldType.esriFieldTypeSingle:
                fieldInfo.NumberFormat = (INumberFormat)numberFormat;
                break;
            case esriFieldType.esriFieldTypeString:
                if (fieldName.Contains("NAM")) {
                    // Primary display field
                    geoFeatureLayer.DisplayField = field.Name;
                }
                break;
        }

        if (FieldAliasTable.Contains(fieldName)) {
            fieldAlias = FieldAliasTable[fieldName].ToString();
        }

        fieldInfo.Alias = fieldAlias;
    }
}
■ ObjectClass(FeatureClass, Table)의 필드 Alias 변경
 - 다음 코드는 ArcCatalog에서 각각의 필드에 대한 별칭을 UI상에서 적용하는 과정을 코드로 구현한 예임
 - 클래스나 필드의 별칭을 변경하기 위해서는 반드시 GeoDatabase에 등록되어 있어야 한다.
public void AlterFieldAlias(IObjectClass objectClass, string fieldName, string aliasName) {
    if (objectClass.ObjectClassID == -1) {
        // string suggestedOIDFieldName = "OBJECTID";
        // RegisterWithGeodatabase(objectClass, suggestedOIDFieldName);
        return;
    }
    
    // Attempt to acquire an exclusive schema lock for the object class.
    ISchemaLock schemaLock = (ISchemaLock)objectClass;
    try {
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

        IClassSchemaEdit schemaEdit = (IClassSchemaEdit)objectClass;
        if (objectClass.FindField(fieldName) != -1) {
            schemaEdit.AlterFieldAliasName(fieldName, aliasName);
        }
    } catch (COMException comExc) {
        throw comExc;
    } finally {
        // Reset the lock on the object class to a shared lock.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
    }
}
■ ObjectClass의 GeoDatabase 등록
 - 다음 코드는 위 그림과 같이 GeoDatabase에 등록되지 않은 테이블이나 FeatureClass를 등록하는 예임
public void RegisterWithGeodatabase(IObjectClass objectClass, string suggestedOIDFieldName) {
    // Attempt to acquire an exclusive schema lock for the object class.
    ISchemaLock schemaLock = (ISchemaLock)objectClass;
    try {
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

        IClassSchemaEdit classSchemaEdit = (IClassSchemaEdit)objectClass;
        classSchemaEdit.RegisterAsObjectClass(suggestedOIDFieldName, string.Empty);
    } catch (COMException comExc) {
        throw comExc;
    } finally {
        // Reset the lock on the object class to a shared lock.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
    }
}

2011년 11월 23일 수요일

[ArcObjects]How to identify on a map layer

▣ ArcObjects Interface
 - IIdentify

▣ Code Snippet
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.esriSystem;
■ Identify on a map
public static void IdentifyMap(IMap focusMap, IPoint mapPoint) {
    if (focusMap.LayerCount == 0) return;

    ESRI.ArcGIS.esriSystem.UID uid = new UIDClass();
    uid.Value = "{6CA416B1-E160-11D2-9F4E-00C04F6BC78E}";

    IEnumLayer enumLayer = focusMap.get_Layers(uid, true);
    enumLayer.Reset();

    ILayer layer = enumLayer.Next();
    while (layer != null) {
        if (layer.Valid && layer.Visible) {
            IdentifyLayer(focusMap, layer, mapPoint);
        }
        layer = enumLayer.Next();
    }
}
■ Identify on a layer
static void IdentifyLayer(IMap focusMap, ILayer layer, IPoint mapPoint) {
    bool isFeatureLayer = layer is IFeatureLayer;

    IIdentify identify = (IIdentify)layer;
    IGeometry searchGeometry = mapPoint;

    if (isFeatureLayer) {
        IFeatureLayer featureLayer = (IFeatureLayer)layer;
        if (featureLayer.FeatureClass.ShapeType != esriGeometryType.esriGeometryPolygon) {
            ITopologicalOperator topoOpt = (ITopologicalOperator)mapPoint;
            double radius = PixelsToMapUnits((IActiveView)focusMap, 6d);

            searchGeometry = topoOpt.Buffer(radius);
        }
    }

    ESRI.ArcGIS.esriSystem.IArray idArray = identify.Identify(searchGeometry);
    if (idArray == null) {
        return;
    }

    IActiveView activeView = (IActiveView)focusMap;

    for (int k = 0; k < idArray.Count; k++) {
        IIdentifyObj identifyObj = (IIdentifyObj)idArray.get_Element(k);

        if (isFeatureLayer) {
            IFeatureIdentifyObj featureObj = (IFeatureIdentifyObj)identifyObj;
            IRowIdentifyObject rowObj = (IRowIdentifyObject)featureObj;
            IFeature feature = (IFeature)rowObj.Row;

            // flash shape
            identifyObj.Flash(activeView.ScreenDisplay);

            // show attributes
            string infoMsg = string.Format("{0}'s OID = {1}", layer.Name, feature.OID);
            System.Windows.Forms.MessageBox.Show(infoMsg);
        } else {
            IRasterIdentifyObj rasterObj = (IRasterIdentifyObj)identifyObj;

            // show attributes
            string infoMsg = string.Format("{0}'s pixel value = {1}", layer.Name, rasterObj.Name);
            System.Windows.Forms.MessageBox.Show(infoMsg);
        }
    }
}
■ Convert screen pixels to map units
static double PixelsToMapUnits(IActiveView activeView, double pixelUnits) {
    IDisplayTransformation displayTrans = activeView.ScreenDisplay.DisplayTransformation;
    tagRECT deviceRECT = displayTrans.get_DeviceFrame();
    int pixelExtent = deviceRECT.right - deviceRECT.left;

    double realWorldDisplayExtent = displayTrans.VisibleBounds.Width;
    double sizeOfOnePixel = realWorldDisplayExtent / pixelExtent;

    return pixelUnits * sizeOfOnePixel;
}