[postgis-users] label placement
Chapman, Martin
MChapman at sanz.com
Thu Jun 1 13:14:19 PDT 2006
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
More information about the postgis-users
mailing list