[postgis-users] label placement
Christo Du Preez
christo at mecola.com
Thu Jun 1 13:17:58 PDT 2006
Hi Martin
Thanx very much, I'll give it a go.
I appreciate your effort.
Chapman, Martin wrote:
> Christo,
>
> Bruce is right. Label placement is gui thing, not really a database
> thing. I mean you could use some of the db functions to help you find
> the right placement but it's really a job for the ui.
>
> Here's how I do it.
>
>
> 1. For each visible feature in the viewport I calculate the label x,y
> location. Note - avoid calculating placements for non-visible features.
>
> 2. I measure the text label with the device context and calculate a
> bounding rectangle from the string measurements and center the rect
> around the label location point.
>
> 3. I search an r-tree index I have for labels to see if the new label
> rect overlaps any other rect in the r-tree. If it doesn't intersect any
> other label rects then I add the new rect to my r-tree and draw my
> label.
>
> 4. I recalculate my label r-tree index every time I pan or zoom my map.
>
> This solution works completely awesome and is fast as hect.
>
> See the fllowing code to see how I do it.
>
>
>
> Code Functions:
>
> void CMapView::DrawLabels(Graphics* pGraphics, Graphics* pMemGraphics,
> IVPLayer* pLayer, CRect& clientRect)
> {
> try
> {
> if (dynamic_cast< IVPLayerCollection*>(pLayer))
> {
> list< IVPLayer*>::iterator it;
> list< IVPLayer*>* pList =
> ((IVPLayerCollection*)pLayer)->GetLayers();
> for (it = pList->begin(); it != pList->end();
> it++)
> DrawLabels(pGraphics, pMemGraphics, *it,
> clientRect);
> }
> else if (dynamic_cast< IVPLayer*>(pLayer))
> {
> if (dynamic_cast< IVPVectorLayer*> (pLayer))
> {
> CVPPtr< IVPVectorLayer> spVectorLayer =
> (IVPVectorLayer*) pLayer;
> CVPPtr< IVPGraphicCollection>
> spGraphicCollection = spVectorLayer->GetGraphicCollection();
>
> if (spGraphicCollection.p)
> {
> CVPPtr< IVPVectorSet>
> spVectorSet = (IVPVectorSet*) spVectorLayer->GetResultSet();
> CVPPtr< IVPFeatureStyle>
> spFeatureStyle = spVectorLayer->GetFeatureStyle();
> VPWkbGeometryType eGeomType =
> (VPWkbGeometryType) spVectorLayer->GetGeometryTypeId();
> CGDIGeometryCollection*
> pGDIGeometryCollection = (CGDIGeometryCollection*)
> spGraphicCollection->GetPath();
> vector< CGDIGeometry*>*
> pGDIGeometries = pGDIGeometryCollection->geometries;
> CGDIGeometry* pGeometry = NULL;
>
>
> pMemGraphics->SetTextRenderingHint(TextRenderingHintAntiAlias);
> StringFormat* pStringFormat =
> (StringFormat*) StringFormat::GenericTypographic()->Clone();
>
> pStringFormat->SetAlignment(StringAlignmentCenter);
>
> Gdiplus::Font* pFont =
> (Gdiplus::Font*) spGraphicCollection->GetFont();
> FontFamily fontFamily;
> pFont->GetFamily(&fontFamily);
>
> TEXTMETRIC textMetrics;
> HDC hdc =
> pMemGraphics->GetHDC();
> ::GetTextMetrics(hdc,
> &textMetrics);
> pMemGraphics->ReleaseHDC(hdc);
>
> REAL nDCHeight =
> textMetrics.tmHeight;
> REAL nDCRatio = nDCHeight /
> 16.0f;
> REAL nFontSize =
> pFont->GetSize();
> REAL nFontRatio = nFontSize /
> 11.0f;
> REAL nSize = nFontSize *
> nFontRatio * nDCRatio;
>
> Gdiplus::Font font(&fontFamily,
> (REAL) nSize, pFont->GetStyle(), pFont->GetUnit());
> Brush* pFontBrush = (Brush*)
> spGraphicCollection->GetFontBrush();
> Brush* pLabelBrush = (Brush*)
> spGraphicCollection->GetLabelBrush();
> Pen* pLabelLinePen = (Pen*)
> spGraphicCollection->GetLabelLinePen();
> PointF location;
> WCHAR* pwText = NULL;
> string sText = "";
> int nLabelFieldIndex =
> spVectorLayer->GetLabelIndex();
> auto_ptr< PointF> pOrigin(new
> PointF(0.0f, 0.0f));
> RectF boundRect;
> int nTextSize = 0;
> int nBkMode =
> spFeatureStyle->GetBkMode();
> float aMin[2];
> float aMax[2];
> int nHit = 0;
>
> long nFID = 0;
> long nGeometryCount = (long)
> pGDIGeometries->size();
> spVectorSet->Reset();
> while (spVectorSet->Next())
> {
> if
> (!spVectorSet->IsEnabled()) continue;
> nFID =
> spVectorSet->GetFID();
> if (nFID >=
> nGeometryCount) continue;
> pGeometry =
> pGDIGeometries->at(nFID);
>
> if (nLabelFieldIndex ==
> 0)
> {
> stringstream
> sStream;
> sStream << nFID;
> sText =
> sStream.str();
> }
> else
> sText = (string)
> spVectorSet->GetString(nLabelFieldIndex - 1);
>
> nTextSize = (int)
> sText.size();
>
> if (sText.size() > 0)
> {
> if
> (GetLabelLocation(pGeometry, &location))
> {
>
> USES_CONVERSION;
> pwText =
> T2W(sText.c_str());
>
> pMemGraphics->MeasureString(pwText, nTextSize, &font, location,
> pStringFormat, &boundRect);
>
> pOrigin->X = location.X * m_nScaleX + -m_nTranslationX;
>
> if
> (eGeomType == VPWkbPoint || eGeomType == VPWkbMultiPoint)
> {
>
> pOrigin->Y = (location.Y * m_nScaleY + -m_nTranslationY) -
> (boundRect.Height);
> }
> else if
> (eGeomType == VPWkbLineString || eGeomType == VPWkbMultiLineString)
> {
>
> pOrigin->Y = (location.Y * m_nScaleY + -m_nTranslationY) -
> (boundRect.Height / 2.0f);
> }
> else
> {
>
> pOrigin->Y = (location.Y * m_nScaleY + -m_nTranslationY) -
> (boundRect.Height / 2.0f);
> }
>
>
> boundRect.X = pOrigin->X - (boundRect.Width / 2.0f);
>
> boundRect.Y = pOrigin->Y;
>
> aMin[0]
> = boundRect.X;
> aMin[1]
> = -boundRect.GetBottom();
> aMax[0]
> = boundRect.GetRight();
> aMax[1]
> = -boundRect.Y;
>
> nHit =
> m_pLabelRTree->Search(aMin, aMax, TreeSearchCallback, NULL);
> if (nHit
> == 0)
> {
>
> m_pLabelRTree->Insert(aMin, aMax, nFID);
>
>
> if (nBkMode == 1)
>
> {
>
> pMemGraphics->FillRectangle(pLabelBrush, boundRect);
>
> pMemGraphics->DrawRectangle(pLabelLinePen, boundRect);
>
> }
>
>
> pMemGraphics->DrawString(pwText, -1, &font, *pOrigin, pStringFormat,
> pFontBrush);
> }
> }
> }
> }
>
> if (pStringFormat) delete
> pStringFormat;
> }
> }
> }
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::DrawLayer().";}
> }
>
> bool CMapView::GetLabelLocation(CGDIGeometry* pGeometry, PointF*
> pLocation)
> {
> bool bSucceeded = false;
>
> try
> {
> if (!pGeometry || !pLocation) return false;
>
> int nGeometryTypeId = pGeometry->geometryType;
> if (nGeometryTypeId == wkbPoint)
> {
> CGDIPoint* pPoint = (CGDIPoint*) pGeometry;
> if (!pPoint->center) return false;
> pLocation->X = pPoint->center->X;
> pLocation->Y = pPoint->center->Y;
> }
> else if (nGeometryTypeId == wkbLineString)
> {
> CGDILineString* pLineString = (CGDILineString*)
> pGeometry;
> GetMidPoint(pLineString->points,
> pLineString->numPoints, pLocation);
> }
> else if (nGeometryTypeId == wkbPolygon)
> {
> CGDIPolygon* pPolygon = (CGDIPolygon*)
> pGeometry;
> if (!pPolygon->rings->at(0)->points) return
> false;
> GetCentroid(pPolygon->rings->at(0)->points,
> pPolygon->rings->at(0)->numPoints, pLocation);
> }
> else if (nGeometryTypeId == wkbMultiPoint)
> {
> CGDIMultiPoint* pMultiPoint = (CGDIMultiPoint*)
> pGeometry;
> if (!pMultiPoint->geometries->at(0)->labelPoint)
> return false;
>
> GetCentroid(pMultiPoint->geometries->at(0)->labelPoint, 1, pLocation);
> }
> else if (nGeometryTypeId == wkbMultiLineString)
> {
> int nIndex = 0;
> CGDILineString* pLineString = NULL;
> double nDistance = 0.0, nMaxDistance = 0.0;
> CGDIMultiLineString* pMultiLineString =
> (CGDIMultiLineString*) pGeometry;
> for (int i = 0; i <
> pMultiLineString->numLineStrings; i++)
> {
> pLineString = (CGDILineString*)
> pMultiLineString->geometries->at(i);
> nDistance =
> GetDistance(pLineString->points, pLineString->numPoints);
> if (nDistance > nMaxDistance)
> {
> nMaxDistance = nDistance;
> nIndex = i;
> }
> }
>
> pLineString = (CGDILineString*)
> pMultiLineString->geometries->at(nIndex);
> GetMidPoint(pLineString->points,
> pLineString->numPoints, pLocation);
> }
> else if (nGeometryTypeId == wkbMultiPolygon)
> {
> CGDIMultiPolygon* pMultiPolygon =
> (CGDIMultiPolygon*) pGeometry;
> double nMaxArea = -1.0;
> CGDIPolygon* pPolygon = NULL;
> GetMaxAreaPolygon(pMultiPolygon, &nMaxArea,
> &pPolygon);
> if (!pPolygon) return false;
> GetCentroid(pPolygon->rings->at(0)->points,
> pPolygon->rings->at(0)->numPoints, pLocation);
> }
>
> return true;
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::GetLabelLocation().";}
>
> return false;
> }
>
> void CMapView::GetMidPoint(PointF* pPoints, int nPointCount, PointF*
> pMidPoint)
> {
> try
> {
> if (!pPoints || !pMidPoint)
> throw "Invalid parameter null in
> CMapView::GetMidPoint()";
>
> double nY1 = 0.0, nX1 = 0.0, nY2 = 0.0, nX2 = 0.0,
> nDistance = 0.0;
> nDistance = GetDistance(pPoints, nPointCount);
> double nMidPoint = nDistance / 2.0;
> nDistance = 0.0f;
>
> for (int i = 0; i < nPointCount - 1; i++)
> {
> if (i == 0)
> {
> nX1 = pPoints[i].X;
> nY1 = pPoints[i].Y;
> i++;
> }
>
> nX2 = pPoints[i].X;
> nY2 = pPoints[i].Y;
>
> nDistance += (double) sqrt(((nX2 - nX1) * (nX2 -
> nX1)) + ((nY2 - nY1) * (nY2 - nY1)));
>
> if (nDistance > nMidPoint)
> {
> nY2 = nY1 + ((nY2 - nY1) / 2.0);
> nX2 = nX1 + ((nX2 - nX1) / 2.0);
> break;
> }
>
> nY1 = nY2;
> nX1 = nX2;
> }
>
> pMidPoint->X = (REAL)nX2;
> pMidPoint->Y = (REAL)nY2;
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::GetMidPoint().";}
> }
>
> double CMapView::GetDistance(PointF* pPoints, int nPointCount)
> {
> try
> {
> double nY1 = 0.0, nX1 = 0.0, nY2 = 0.0, nX2 = 0.0,
> nDistance = 0.0;
> int j = 0;
>
> for (int i = 0; i < nPointCount - 1; i++)
> {
> if (i == 0)
> {
> nX1 = pPoints[i].X;
> nY1 = pPoints[i].Y;
> i++;
> }
>
> nX2 = pPoints[i].X;
> nY2 = pPoints[i].Y;
>
> nDistance += sqrt(((nX2 - nX1) * (nX2 - nX1)) +
> ((nY2 - nY1) * (nY2 - nY1)));
>
> nY1 = nY2;
> nX1 = nX2;
> }
>
> return nDistance;
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::GetDistance().";}
> }
>
> void CMapView::GetCentroid(PointF* pPoints, int nPointCount, PointF*
> pMidPoint)
> {
> try
> {
> if (!pPoints || !pMidPoint) return;
>
> double cent_weight_x = 0.0, cent_weight_y = 0.0;
> double len, total_len = 0;
> double x1, y1, x2, y2;
>
> x2 = (double) pPoints[0].X;
> y2 = (double) pPoints[0].Y;
>
> for (int i = 1; i < (int) nPointCount; i++)
> {
> x1 = x2;
> y1 = y2;
> x2 = pPoints[i].X;
> y2 = pPoints[i].Y;
>
> len = sqrt(pow((x2 - x1), 2) + pow((y2 - y1),
> 2));
> cent_weight_x += len * ((x1 + x2) / 2.0);
> cent_weight_y += len * ((y1 + y2) / 2.0);
> total_len += len;
> }
>
> if(total_len == 0) return;
> pMidPoint[0].X = (REAL) (cent_weight_x / total_len);
> pMidPoint[0].Y = (REAL) (cent_weight_y / total_len);
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::GetCentroid().";}
> }
>
> void CMapView::GetMaxAreaPolygon(CGDIMultiPolygon* pMultiPolygon,
> double* nMaxArea, CGDIPolygon** ppPolygon)
> {
> try
> {
> if (!pMultiPolygon) return;
>
> vector< CGDIGeometry*>::iterator it;
> vector< CGDIGeometry*>* pGDIGeometries =
> pMultiPolygon->geometries;
>
> for (it = pGDIGeometries->begin(); it !=
> pGDIGeometries->end(); it++)
> {
> CGDIGeometry* pGeometry = *it;
> if (pGeometry->geometryType == wkbMultiPolygon)
> {
> CGDIMultiPolygon* pGDIMultiPolygon =
> (CGDIMultiPolygon*) pGeometry;
> GetMaxAreaPolygon(pGDIMultiPolygon,
> nMaxArea, ppPolygon);
> }
> else if (pGeometry->geometryType == wkbPolygon)
> {
> CGDIPolygon* pGDIPolygon =
> (CGDIPolygon*) pGeometry;
> double nArea =
> GetArea(pGDIPolygon->rings->at(0)->points,
> pGDIPolygon->rings->at(0)->numPoints);
> for (int r = 1; r <
> pGDIPolygon->numRings; r++)
> nArea +=
> GetArea(pGDIPolygon->rings->at(r)->points,
> pGDIPolygon->rings->at(r)->numPoints);
>
> if (nArea > (*nMaxArea))
> {
> *nMaxArea = nArea;
> *ppPolygon = (CGDIPolygon*)
> pGDIPolygon;
> }
> }
> }
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::GetMaxAreaGeometry().";}
> }
>
> double CMapView::GetArea(PointF* pPoints, int nPointCount)
> {
> try
> {
> int j = 0;
> double nArea = 0.0;
>
> if (nPointCount < 2)
> return 0.0;
>
> for (int i = 0; i < nPointCount; ++i)
> {
> j = (i + 1) % nPointCount;
> nArea += pPoints[i].X * pPoints[j].Y;
> nArea -= pPoints[i].Y * pPoints[j].X;
> }
>
> nArea = nArea / 2.0;
> return (nArea < 0.0 ? -nArea : nArea);
> }
> catch(exception e)
> {throw e;}
> catch(char* e)
> {throw e;}
> catch(...)
> {throw "An unexpected error occurred in
> CMapView::GetArea().";}
> }
>
> Martin
>
>
>
>
> -----Original Message-----
> From: postgis-users-bounces at postgis.refractions.net
> [mailto:postgis-users-bounces at postgis.refractions.net] On Behalf Of
> Bruce Rindahl
> Sent: Thursday, June 01, 2006 8:42 AM
> To: 'PostGIS Users Discussion'
> Subject: RE: [postgis-users] label placement
>
> OK I'll take this one ;)
> I can assure you that PostGIS will NEVER give town labels that overlap.
> It
> will return the labels in a nice neat table, one per row.
> Christo you seem to be asking how to place labels on some kind of
> mapping
> interface. While PostGIS is used extensively by mapping interfaces, it
> is a
> database, not mapping software.
> Check the discussion lists of the mapping software or interface you are
> using. (Mapserver, GRASS, ESRI, Mapinfo....,?)
> By the way, in my 12+ years in GIS I think this is probably THE most
> difficult issue - placing labels. You are NEVER going to find an
> automatic
> label (annotation) placement routine that will work for all cases. I am
> sure you will find lots of suggestions on appropriate list.
> Good Luck!
> Bruce Rindahl
>
> -----Original Message-----
> From: postgis-users-bounces at postgis.refractions.net
> [mailto:postgis-users-bounces at postgis.refractions.net] On Behalf Of
> Christo
> Du Preez
> Sent: Thursday, June 01, 2006 7:02 AM
> To: PostGIS Users Discussion
> Subject: [postgis-users] label placement
>
> Hi all,
>
> I'm sure this question has been asked a million times. Unfortunately the
>
> search on http://postgis.refractions.net/cgi-bin/htsearch don't work.
>
> My question... How can I write a query that avoids town labels from
> overlapping?
>
> Regards,
> Christo
> _______________________________________________
> postgis-users mailing list
> postgis-users at postgis.refractions.net
> http://postgis.refractions.net/mailman/listinfo/postgis-users
>
> _______________________________________________
> postgis-users mailing list
> postgis-users at postgis.refractions.net
> http://postgis.refractions.net/mailman/listinfo/postgis-users
> _______________________________________________
> postgis-users mailing list
> postgis-users at postgis.refractions.net
> http://postgis.refractions.net/mailman/listinfo/postgis-users
>
>
>
--
Christo Du Preez
Senior Software Engineer
Mecola IT
+27 [0]83 326 8087
More information about the postgis-users
mailing list