[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