11package geoscript.carto
22
33import geoscript.feature.Schema
4+ import geoscript.geom.Bounds
45import geoscript.geom.Geometry
6+ import geoscript.geom.LineString
57import geoscript.layer.Layer
68import geoscript.proj.Projection
79import geoscript.render.Map
10+ import geoscript.style.ColorMap
811import geoscript.workspace.Memory
912
1013import javax.imageio.ImageIO
@@ -20,6 +23,7 @@ import java.awt.font.TextAttribute
2023import java.awt.geom.GeneralPath
2124import java.text.AttributedString
2225import java.text.DecimalFormat
26+ import java.text.NumberFormat
2327import java.text.SimpleDateFormat
2428
2529/**
@@ -122,7 +126,7 @@ class Java2DCartoBuilder implements CartoBuilder {
122126 FontMetrics fontMetrics = graphics. fontMetrics
123127 String text = " N"
124128 int textHeight = fontMetrics. height
125- drawString(text, new Rectangle (0 , height - textHeight, width, textHeight), HorizontalAlign . CENTER , VerticalAlign . BOTTOM )
129+ drawString(text, new Rectangle (x, y + ( height - textHeight) , width, textHeight), HorizontalAlign . CENTER , VerticalAlign . BOTTOM )
126130 height = height - textHeight
127131 }
128132
@@ -164,10 +168,10 @@ class Java2DCartoBuilder implements CartoBuilder {
164168 FontMetrics fontMetrics = graphics. fontMetrics
165169 int textWidth = [fontMetrics. stringWidth(" N" ), fontMetrics. stringWidth(" E" ), fontMetrics. stringWidth(" S" ), fontMetrics. stringWidth(" W" )]. max()
166170 int textHeight = fontMetrics. height
167- drawString(" N" , new Rectangle ((width / 2 - textWidth / 2 ) as int , 0 , textWidth, textHeight), HorizontalAlign . CENTER , VerticalAlign . TOP )
168- drawString(" E" , new Rectangle (width - textWidth, (height / 2 - textHeight / 2 ) as int , textWidth, textHeight), HorizontalAlign . RIGHT , VerticalAlign . MIDDLE )
169- drawString(" S" , new Rectangle ((width / 2 - textWidth / 2 ) as int , height - textHeight, textWidth, textHeight), HorizontalAlign . CENTER , VerticalAlign . TOP )
170- drawString(" W" , new Rectangle (0 , (height / 2 - textHeight / 2 ) as int , textWidth, textHeight), HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
171+ drawString(" N" , new Rectangle (x + (width / 2 - textWidth / 2 ) as int , y , textWidth, textHeight), HorizontalAlign . CENTER , VerticalAlign . TOP )
172+ drawString(" E" , new Rectangle (x + ( width - textWidth), y + (height / 2 - textHeight / 2 ) as int , textWidth, textHeight), HorizontalAlign . RIGHT , VerticalAlign . MIDDLE )
173+ drawString(" S" , new Rectangle (x + (width / 2 - textWidth / 2 ) as int , y + ( height - textHeight) , textWidth, textHeight), HorizontalAlign . CENTER , VerticalAlign . TOP )
174+ drawString(" W" , new Rectangle (x, y + (height / 2 - textHeight / 2 ) as int , textWidth, textHeight), HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
171175 x = x + textWidth
172176 y = y + textHeight
173177 width = width - (textWidth * 2 )
@@ -398,6 +402,170 @@ class Java2DCartoBuilder implements CartoBuilder {
398402 this
399403 }
400404
405+ @Override
406+ CartoBuilder legend (LegendItem legendItem ) {
407+
408+ // Draw background
409+ if (legendItem. backgroundColor) {
410+ graphics. color = legendItem. backgroundColor
411+ graphics. fillRect(legendItem. x, legendItem. y, legendItem. width, legendItem. height)
412+ }
413+
414+ // Draw title
415+ graphics. font = legendItem. titleFont
416+ graphics. color = legendItem. titleColor
417+ FontMetrics fm = graphics. fontMetrics
418+ int titleHeight = fm. height
419+ drawString(legendItem. title, new Rectangle (legendItem. x, legendItem. y, legendItem. width, titleHeight), HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
420+
421+ // Draw Entries
422+ graphics. font = legendItem. textFont
423+ graphics. color = legendItem. textColor
424+ fm = graphics. fontMetrics
425+
426+ // Keep track of the entry x and y
427+ int entryX = legendItem. x
428+ int entryY = legendItem. y + titleHeight + legendItem. gapBetweenEntries
429+ int maxTextWidth = -1
430+
431+ legendItem. entries. eachWithIndex { LegendItem.LegendEntry entry , int i ->
432+
433+ if (entry. type == LegendItem.LegendEntryType . COLORMAP ) {
434+
435+ // Draw title
436+ graphics. color = legendItem. textColor
437+ Rectangle titleRectangle = new Rectangle (entryX, entryY, legendItem. legendEntryWidth * 2 , fm. height)
438+ drawString(entry. title, titleRectangle, HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
439+ entryY + = fm. height + legendItem. gapBetweenEntries
440+
441+ NumberFormat numberFormat = new DecimalFormat (legendItem. numberFormat)
442+ ColorMap colorMap = entry. symbolizer as ColorMap
443+ colorMap. values. each { java.util.Map value ->
444+
445+ String title = numberFormat. format(value. quantity)
446+ String colorHex = " ${ value.color} "
447+
448+ Rectangle symbolRectangle = new Rectangle (
449+ entryX,
450+ entryY,
451+ legendItem. legendEntryWidth,
452+ legendItem. legendEntryHeight
453+ )
454+ graphics. color = Color . decode(colorHex)
455+ graphics. fill(symbolRectangle)
456+
457+ Rectangle textRectangle = new Rectangle (
458+ entryX + legendItem. legendEntryWidth + legendItem. gapBetweenEntries,
459+ entryY,
460+ legendItem. legendEntryWidth,
461+ legendItem. legendEntryHeight
462+ )
463+ maxTextWidth = Math . max(maxTextWidth, fm. stringWidth(title) + legendItem. gapBetweenEntries)
464+ graphics. color = legendItem. textColor
465+ drawString(title, textRectangle, HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
466+
467+ entryY + = legendItem. legendEntryHeight
468+ }
469+
470+ entryY + = legendItem. gapBetweenEntries
471+
472+ } else if (entry. type == LegendItem.LegendEntryType . IMAGE ) {
473+
474+ // Draw Image
475+ Rectangle imageRectangle = new Rectangle (entryX, entryY, legendItem. legendEntryWidth, legendItem. legendEntryHeight)
476+ graphics. drawImage(entry. image, imageRectangle. x as int , imageRectangle. y as int , imageRectangle. width as int , imageRectangle. height as int , null )
477+
478+ // Draw title
479+ graphics. color = legendItem. textColor
480+ Rectangle titleRectangle = new Rectangle (
481+ entryX + legendItem. legendEntryWidth + legendItem. gapBetweenEntries,
482+ entryY,
483+ legendItem. legendEntryWidth,
484+ legendItem. legendEntryHeight
485+ )
486+ drawString(entry. title, titleRectangle, HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
487+
488+ entryY + = legendItem. legendEntryHeight + legendItem. gapBetweenEntries
489+
490+ } else if (entry. type == LegendItem.LegendEntryType . GROUP ) {
491+
492+ // Draw title
493+ graphics. color = legendItem. textColor
494+ Rectangle titleRectangle = new Rectangle (entryX, entryY, legendItem. legendEntryWidth * 2 , fm. height)
495+ drawString(entry. title, titleRectangle, HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
496+ entryY + = fm. height + legendItem. gapBetweenEntries
497+
498+ } else /* POINT, LINE, POLYGON */ {
499+
500+ Rectangle symbolRectangle = new Rectangle (
501+ entryX,
502+ entryY,
503+ legendItem. legendEntryWidth,
504+ legendItem. legendEntryHeight
505+ )
506+
507+ Geometry geometry
508+ if (entry. type == LegendItem.LegendEntryType . POLYGON ) {
509+ geometry = new Bounds (2 , 2 , legendItem. legendEntryWidth - 2 , legendItem. legendEntryHeight - 2 ). geometry
510+ } else if (entry. type == LegendItem.LegendEntryType . LINE ) {
511+ geometry = new LineString ([[0 , legendItem. legendEntryHeight / 2], [legendItem.legendEntryWidth, legendItem.legendEntryHeight / 2 ]])
512+ } else if (entry. type == LegendItem.LegendEntryType . POINT ) {
513+ geometry = new geoscript.geom.Point ((legendItem. legendEntryWidth / 2) as int, (legendItem.legendEntryHeight / 2 ) as int )
514+ }
515+
516+ Layer layer = Layer . fromGeometry(" polygon" , geometry, style : entry. symbolizer)
517+ Map map = new Map (
518+ width : legendItem. legendEntryWidth,
519+ height : legendItem. legendEntryHeight,
520+ fixAspectRatio : false ,
521+ bounds : new Bounds (0 , 0 , legendItem. legendEntryWidth, legendItem. legendEntryHeight),
522+ layers : [layer]
523+ )
524+ graphics. drawImage(map. renderToImage(), symbolRectangle. x as int , symbolRectangle. y as int , null )
525+
526+ Rectangle textRectangle = new Rectangle (
527+ entryX + legendItem. legendEntryWidth + legendItem. gapBetweenEntries,
528+ entryY,
529+ legendItem. legendEntryWidth,
530+ legendItem. legendEntryHeight
531+ )
532+ maxTextWidth = Math . max(maxTextWidth, fm. stringWidth(entry. title) + legendItem. gapBetweenEntries)
533+ drawString(entry. title, textRectangle, HorizontalAlign . LEFT , VerticalAlign . MIDDLE )
534+
535+ entryY + = legendItem. legendEntryHeight + legendItem. gapBetweenEntries
536+ }
537+
538+ // Are there more entries?
539+ if (i < (legendItem. entries. size() - 1 )) {
540+ // Check to make sure the next entry can fit in the remaining space
541+ int nextHeight = getLegendItemHeight(legendItem, legendItem. entries[i + 1 ])
542+ if ((entryY + nextHeight) > legendItem. height) {
543+ // Move the entry x over to the right to a separate column
544+ entryX + = legendItem. legendEntryWidth + legendItem. gapBetweenEntries + maxTextWidth
545+ // Reset the entry y to the top
546+ entryY = legendItem. y + titleHeight + legendItem. gapBetweenEntries
547+ // Reset the max text width
548+ maxTextWidth = -1
549+ }
550+ }
551+ }
552+ this
553+ }
554+
555+ private int getLegendItemHeight (LegendItem item , LegendItem.LegendEntry entry ) {
556+ int height
557+ if (entry. type == LegendItem.LegendEntryType . COLORMAP ) {
558+ graphics. font = item. textFont
559+ height = graphics. fontMetrics. height + item. gapBetweenEntries + (entry. symbolizer. values. size() * item. legendEntryHeight) + item. gapBetweenEntries
560+ } else if (entry. type == LegendItem.LegendEntryType . GROUP ) {
561+ graphics. font = item. textFont
562+ height = graphics. fontMetrics. height + item. gapBetweenEntries
563+ } else {
564+ height = item. legendEntryHeight + item. gapBetweenEntries
565+ }
566+ height
567+ }
568+
401569 private void drawString (String text , Rectangle rectangle , HorizontalAlign horizontalAlign , VerticalAlign verticalAlign ) {
402570 drawString(text, rectangle, horizontalAlign, verticalAlign, false )
403571 }
0 commit comments