Point p = /*a point*/;
ReferencedEnvelope envelope = mapPane.getDisplayArea();
/*envelope.moveTo or something like that? */
mapPane.setDisplayArea(envelope);
How can I move the envelope so that the center position is the position of the given point? All I see are "expand"-methods.
The easiest way is to create a new envelope centred on your new point:
ReferencedEnvelope env = fr.getMapPane().getDisplayArea();
double x,y;//new centre
double w2 = env.getWidth()/2.0;
double h2 = env.getHeight()/2.0;
Coordinate c = new Coordinate(x,y);
ReferencedEnvelope renv = new ReferencedEnvelope(c.x-w2,c.x+w2,c.y-h2,c.y+h2,env.getCoordinateReferenceSystem());
Related
I want to plot a line which connects 2 points on a map.
The code I am using:
public class Quickstart {
public static void main(String[] args) throws Exception {
// display a data store file chooser dialog for shapefiles
File file = JFileDataStoreChooser.showOpenFile("shp", null);
if (file == null) {
return;
}
FileDataStore store = FileDataStoreFinder.getDataStore(file);
SimpleFeatureSource featureSource = store.getFeatureSource();
GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
// ask for current and destination positions
double latitude, longitude, latitudeDest, longitudeDest;
Scanner reader = new Scanner(System.in);
reader.useLocale(Locale.US);
System.out.println("Enter reference longitude and latitude:\n");
longitude = reader.nextDouble();
latitude = reader.nextDouble();
System.out.println("Enter destination longitude and latitude:\n");
longitudeDest = reader.nextDouble();
latitudeDest = reader.nextDouble();
reader.close();
final String EPSG4326 = "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\"," +
"\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\", " +
"0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]]";
CoordinateReferenceSystem crs = CRS.parseWKT(EPSG4326);
Point start = gf.createPoint(new Coordinate(longitude, latitude));
Point end = gf.createPoint(new Coordinate(longitudeDest, latitudeDest));
GeodeticCalculator gc = new GeodeticCalculator(crs);
gc.setStartingPosition(JTS.toDirectPosition(start.getCoordinate(), crs));
gc.setDestinationPosition(JTS.toDirectPosition(end.getCoordinate(), crs));
// Calculate distance between points
double distance = gc.getOrthodromicDistance();
int totalmeters = (int) distance;
int km = totalmeters / 1000;
int meters = totalmeters - (km * 1000);
float remaining_cm = (float) (distance - totalmeters) * 10000;
remaining_cm = Math.round(remaining_cm);
float cm = remaining_cm / 100;
System.out.println("Distance = " + km + "km " + meters + "m " + cm + "cm");
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("TwoDistancesType");
builder.setCRS(DefaultGeographicCRS.WGS84);
builder.add("location", Point.class);
// build the type
final SimpleFeatureType TYPE = builder.buildFeatureType();
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
featureBuilder.add(start);
//featureBuilder.add(end);
SimpleFeature feature = featureBuilder.buildFeature(null);
DefaultFeatureCollection featureCollection = new DefaultFeatureCollection("internal", TYPE);
featureCollection.add(feature);
Style style = SLD.createSimpleStyle(TYPE, Color.red);
Layer layer = new FeatureLayer(featureCollection, style);
// Create a map content and add our shapefile to it
MapContent map = new MapContent();
map.setTitle("TEST");
map.addLayer(layer);
// Now display the map
JMapFrame.showMap(map);
}
}
I have 2 problems:
1) I can't add a second feature to featureBuilder.It doesn't allow it.It shows Can handle 1 attributes only, index is 1.
So, how can I plot a line then?
2) With the above code, I am receiving:
org.geotools.renderer.lite.StreamingRenderer fireErrorEvent SEVERE: The scale denominator must be positive
java.lang.IllegalArgumentException: The scale denominator must be positive
------- UPDATE ------------------------
After the solution that #Michael gave for the first question , now I no longer receive the error regarding the denominator, but I am receiving an empty map (white space).
----- UPDATE according to #iant suggestion ----------------
So, I tried this.Created a coordinates which holds the coordinates of the points (start and end) ,then created a linestring with those coordinates and added it to featurebuilder.
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("TwoDistancesType");
builder.setCRS(DefaultGeographicCRS.WGS84);
builder.add("line", LineString.class); //added a linestring class
final SimpleFeatureType TYPE = builder.buildFeatureType();
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
Coordinate[] coordinates = {start.getCoordinate(), end.getCoordinate()};
LineString line = gf.createLineString(coordinates);
featureBuilder.add(line);
and even though I am loading a map (countries.shp) it shows me an empty white space with a red line.
------ SOLUTION -------------
Ok, so the solution is (thanks to #iants comments):
Style style = SLD.createLineStyle(Color.red, 2.0f);
Layer layer = new FeatureLayer(featureCollection, style);
// Create style for the file
Style shpStyle = SLD.createSimpleStyle(TYPE, Color.blue);
Layer shpLayer = new FeatureLayer(featureSource, shpStyle);
// Create a map content and add our shapefile to it
MapContent map = new MapContent();
map.setTitle("TEST");
map.addLayer(layer);
map.addLayer(shpLayer);
and now you have a red line on a blue map!
You need to create a LineString from your points and then store that in your feature.
You should then get a correct scale but you might want to add some other data such as a coast line to the map first. The quick start tutorial can show you how to do that.
Disclaimer: I've never used geotools.
Looking at the source code of SimpleFeatureBuilder, add calls set which throws that error if:
if(index >= values.length)
throw new ArrayIndexOutOfBoundsException("Can handle "
+ values.length + " attributes only, index is " + index);
values is populated here:
values = new Object[featureType.getAttributeCount()];
so it's obvious that that problem is because your Type only has one property. Change it so it has two:
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("TwoDistancesType");
builder.setCRS(DefaultGeographicCRS.WGS84);
builder.add("start", Point.class);
builder.add("end", Point.class);
How can I create a gradient in PDFBox? Or maybe "can I?".
I don't want to create them and export to jpeg or something else. I need a light document, so this has to be programmed somehow.
Any ideas?
After a lot of research, I finally created a small "creator of my own gradient"! It looks like this:
COSDictionary fdict = new COSDictionary();
fdict.setInt(COSName.FUNCTION_TYPE, 2); // still not understaning that...
COSArray domain = new COSArray();
domain.add(COSInteger.get(0));
domain.add(COSInteger.get(1));
COSArray c0 = new COSArray();
c0.add(COSFloat.get("0.64176"));
c0.add(COSFloat.get("0.72588"));
c0.add(COSFloat.get("0.78078"));
COSArray c1 = new COSArray();
c1.add(COSFloat.get("0.57176"));
c1.add(COSFloat.get("0.62588"));
c1.add(COSFloat.get("0.70078"));
fdict.setItem(COSName.DOMAIN, domain);
fdict.setItem(COSName.C0, c0);
fdict.setItem(COSName.C1, c1);
fdict.setInt(COSName.N, 1);
PDFunctionType2 func = new PDFunctionType2(fdict);
PDShadingType2 axialShading = new PDShadingType2(new COSDictionary());
axialShading.setColorSpace(PDDeviceRGB.INSTANCE);
axialShading.setShadingType(PDShading.SHADING_TYPE2);
COSArray coords1 = new COSArray();
coords1.add(COSInteger.get(0));
coords1.add(COSInteger.get(0));
coords1.add(COSInteger.get(850)); // size of my page
coords1.add(COSInteger.get(600));
axialShading.setCoords(coords1); // so this sets the bounds of my gradient
axialShading.setFunction(func); // and this determines all the curves etc?
CStr.shadingFill(axialShading); // where CStr is a ContentStream for my PDDocument
I will leave this for others. Leave your opinions and be free to show me some clever ideas to improve this code :)
Here's a class I made to make the creation of gradients easier. It supports axial gradients with multiple colors. It uses java.awt.Color to specify colors but that can be replaced easily.
public class PDGradient extends PDShadingType2 {
public PDGradient(List<GradientPart> parts) {
super(new COSDictionary());
// PDF 1.7 - 8.7.4.5.3 Type 2 (Axial) Shadings
setColorSpace(PDDeviceRGB.INSTANCE);
setShadingType(PDShadingType2.SHADING_TYPE2);
setFunction(createGradientFunction(parts));
}
private static PDFunction createGradientFunction(List<GradientPart> parts) {
if (parts.size() < 2) {
throw new IllegalArgumentException("Gradient must have at least 2 colors.");
}
GradientPart first = parts.get(0);
GradientPart last = parts.get(parts.size() - 1);
if (first.ratio != 0f) {
throw new IllegalArgumentException("Gradient first color ratio must be 0.");
} else if (last.ratio != 1f) {
throw new IllegalArgumentException("Gradient last color ratio must be 1.");
}
if (parts.size() == 2) {
// Only two colors, use exponential function.
return createColorFunction(first.color, last.color);
}
// Multiple colors, use stitching function to combine exponential functions
// PDF 1.7 - 7.10.4 Type 3 (Stitching) Functions
COSDictionary dict = new COSDictionary();
COSArray functions = new COSArray();
COSArray bounds = new COSArray();
COSArray encode = new COSArray();
GradientPart lastPart = first;
for (int i = 1; i < parts.size(); i++) {
GradientPart part = parts.get(i);
// Add exponential function for interpolating between these two colors.
functions.add(createColorFunction(lastPart.color, part.color));
// Specify function bounds, except for first and last, which are specified by domain.
if (i != parts.size() - 1) {
bounds.add(new COSFloat(part.ratio));
}
// Used to interpolate stitching function subdomain (eg: [0.2 0.5]
// to the exponential function domain, which is always [0.0 1.0].
encode.add(COSInteger.ZERO);
encode.add(COSInteger.ONE);
lastPart = part;
}
dict.setInt(COSName.FUNCTION_TYPE, 3);
dict.setItem(COSName.DOMAIN, new PDRange()); // [0.0 1.0]
dict.setItem(COSName.FUNCTIONS, functions);
dict.setItem(COSName.BOUNDS, bounds);
dict.setItem(COSName.ENCODE, encode);
return new PDFunctionType3(dict);
}
private static PDFunction createColorFunction(Color start, Color end) {
// PDF 1.7 - 7.10.3 Type 2 (Exponential Interpolation) Functions
COSDictionary dict = new COSDictionary();
dict.setInt(COSName.FUNCTION_TYPE, 2);
dict.setItem(COSName.DOMAIN, new PDRange()); // [0.0 1.0]
dict.setItem(COSName.C0, createColorCOSArray(start));
dict.setItem(COSName.C1, createColorCOSArray(end));
dict.setInt(COSName.N, 1); // Linear interpolation
return new PDFunctionType2(dict);
}
private static COSArray createColorCOSArray(Color color) {
// Create a COSArray for a color.
// java.awt.Color uses 0-255 values while PDF uses 0-1.
COSArray a = new COSArray();
a.add(new COSFloat(color.getRed() / 255f));
a.add(new COSFloat(color.getGreen() / 255f));
a.add(new COSFloat(color.getBlue() / 255f));
return a;
}
/**
* Specifies a color and its position in a {#link PDGradient}.
*/
public static class GradientPart {
public final Color color;
public final float ratio;
public GradientPart(Color color, float ratio) {
this.color = color;
this.ratio = ratio;
}
}
}
Example usage:
List<GradientPart> parts = new ArrayList<>();
parts.add(new GradientPart(Color.RED, 0.0f));
parts.add(new GradientPart(Color.YELLOW, 0.5f));
parts.add(new GradientPart(Color.GREEN, 1.0f));
PDGradient gradient = new PDGradient(parts);
gradient.setCoords(...);
pdfStream.shadingFill(gradient)
This works essentially the same as the other answer for two colors gradients, using an exponential function (type 2) to linearly interpolate between two colors. If there are more colors, a stitching (type 3) function is used to combine multiple exponential functions with different subdomains.
I am trying to extract the bounding boxes of each word from the javacpp tesseract. This appears to be the bounding box call (my full code below):
boolean box = ri.BoundingBox(RIL_WORD, coord1, coord2, coord3, coord4)
RIL_WORD is the iterator level that can be adjusted for words, sentences, and paragraphs. The coordinates are IntPointers (included class with javacpp).
The api says this returns the bounding box coordinates but returns a boolean instead. SO at this point I know there is a bounding box but still cannot get the actual coordinates. Does anyone know how to get the bounding box rectangles out of java cpp tessaract? Thanks for the help. I have posted my working code for getting the individual words and the confidence level below because I had such a hard time finding examples.
public class TesseractOCR {
public void OCRText() {
BytePointer outText;
TessBaseAPI api = new TessBaseAPI();
// Initialize tesseract-ocr with English, without specifying tessdata path
if (api.Init(null, "eng") != 0) {
System.err.println("Could not initialize tesseract.");
System.exit(1);
}
// Open input image with leptonica library
org.bytedeco.javacpp.lept.PIX image = pixRead("testimage.png");
// Get OCR result
outText = api.GetUTF8Text();
System.out.println("OCR output:\n" + outText.getString());
final ResultIterator ri = api.GetIterator();
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
IntPointer coord1 = new IntPointer(x1);
IntPointer coord2 = new IntPointer(y1);
IntPointer coord3 = new IntPointer(x2);
IntPointer coord4 = new IntPointer(y2);
ri.Begin();
if (ri !=null) {
do {
BytePointer word = ri.GetUTF8Text(RIL_WORD);
float conf = ri.Confidence(RIL_WORD);
boolean box = ri.BoundingBox(RIL_WORD, coord1, coord2, coord3, coord4);
System.out.println(word.getString());
System.out.println(conf);
System.out.println(box);
} while (ri.Next(RIL_WORD));
}
api.End();
outText.deallocate();
pixDestroy(image);
}
}
Right now I have:
Polygon circle = geometryBuilder.circle(
myLong,
myLat,
radiusInMeters, 10);
And it creates (with lat=28.456306, long=-16.292034 and radius=500) a nonsense polygon with huge latitudes and longitudes, such as:
POLYGON ((483.678055 28.482505000000003, 388.1865521874737 -265.4101211462366, 138.1865521874737 -447.04575314757676, -170.8304421874737 -447.0457531475768, -420.8304421874737 -265.41012114623663, -516.321945 28.482504999999943, -420.83044218747375 322.3751311462365, -170.8304421874738 504.01076314757677, 138.18655218747358 504.0107631475768, 388.18655218747364 322.3751311462367, 483.678055 28.482505000000003))
I expected to have ten pairs of coordinates with lat's and long's nearby the center point I supplied.
Any help would be more than helpful. Thanks in advance!
EDIT
In addition to #iant 's answer, I had to create a Point as a Feature
//build the type
SimpleFeatureType TYPE = null;
try {
TYPE = DataUtilities.createType("", "Location", "locations:Point:srid=4326," + "id:Integer" // a
// number
// attribute
);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
com.vividsolutions.jts.geom.Point point = geometryFactory.createPoint(
new Coordinate(
currentDevicePosition.getLongitude(),
currentDevicePosition.getLatitude()
)
);
featureBuilder.add(point);
SimpleFeature feature = featureBuilder.buildFeature( "fid.1" ); // build the 1st feature
as explained in iant's Gist here: https://gitlab.com/snippets/17558 and here: http://docs.geotools.org/, oh, and also I was missing a dependency as explained here SchemaException in java
There are two solutions to this:
Convert the radius in meters to degrees and treat the problem as a planar problem
Convert the lat/lon point to meters, calculate the circle in a locally planar projection and reproject back to lat/lon.
For 1 you could do something like which will be fine for small radii near the equator:
GeodeticCalculator calc = new GeodeticCalculator(DefaultGeographicCRS.WGS84);
calc.setStartingGeographicPoint(point.getX(), point.getY());
calc.setDirection(0.0, 10000);
Point2D p2 = calc.getDestinationGeographicPoint();
calc.setDirection(90.0, 10000);
Point2D p3 = calc.getDestinationGeographicPoint();
double dy = p2.getY() - point.getY();
double dx = p3.getX() - point.getX();
double distance = (dy + dx) / 2.0;
Polygon p1 = (Polygon) point.buffer(distance);
I'll show some code for the second as it is more general (i.e. it works better and for a better range of radii).
First you need to find a local projection, GeoTools provides a "pseudo" projection AUTO42001,x,y which is a UTM projection centred at X,Y:
public SimpleFeature bufferFeature(SimpleFeature feature, Measure<Double, Length> distance) {
// extract the geometry
GeometryAttribute gProp = feature.getDefaultGeometryProperty();
CoordinateReferenceSystem origCRS = gProp.getDescriptor().getCoordinateReferenceSystem();
Geometry geom = (Geometry) feature.getDefaultGeometry();
Geometry pGeom = geom;
MathTransform toTransform, fromTransform = null;
// reproject the geometry to a local projection
if (!(origCRS instanceof ProjectedCRS)) {
double x = geom.getCoordinate().x;
double y = geom.getCoordinate().y;
String code = "AUTO:42001," + x + "," + y;
// System.out.println(code);
CoordinateReferenceSystem auto;
try {
auto = CRS.decode(code);
toTransform = CRS.findMathTransform(DefaultGeographicCRS.WGS84, auto);
fromTransform = CRS.findMathTransform(auto, DefaultGeographicCRS.WGS84);
pGeom = JTS.transform(geom, toTransform);
} catch (MismatchedDimensionException | TransformException | FactoryException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
So now pGeom is our point in metres. Buffering it is now easy
Geometry out = bufferFeature(pGeom, distance.doubleValue(SI.METER));
then we project back to WGS84 (lat/lon) using the reverse transform we looked up earlier:
retGeom = JTS.transform(out, fromTransform);
There is then a little messing around to change the feature type to reflect the fact we are returning a polygon instead of a Point. The full code is in this gist.
When I run it I get the following output:
POINT (10.840378413128576 3.4152050343701745)
POLYGON ((10.84937634426605 3.4151876838951822, 10.849200076653755 3.413423962919184, 10.84868480171117 3.4117286878605766, 10.847850322146979 3.4101670058279794, 10.846728706726902 3.4087989300555464, 10.845363057862208 3.407677033830687, 10.843805855306746 3.406844430298736, 10.84211693959797 3.406333115754347, 10.840361212705258 3.4061627400701946, 10.838606144204721 3.4063398515107184, 10.836919178768184 3.4068576449605277, 10.835365144548726 3.4076962232621035, 10.834003762019957 3.408823361646906, 10.832887348980522 3.410195745914279, 10.832058809914859 3.411760636805914, 10.831549986992338 3.4134578966399034, 10.831380436105858 3.4152223003379722, 10.831556675029052 3.416986042039048, 10.832071932633442 3.4186813409639054, 10.832906408849936 3.4202430463705085, 10.834028035422469 3.4216111414662183, 10.835393708241908 3.422733050021835, 10.836950943907517 3.4235656570147763, 10.838639896841123 3.424076965623486, 10.840395659406198 3.4242473268789406, 10.842150756595839 3.4240701947133396, 10.843837739370569 3.4235523773972796, 10.845391776937724 3.4227137757216988, 10.846753148314034 3.4215866180136185, 10.847869537398722 3.4202142214154887, 10.848698043354238 3.4186493270628633, 10.849206829051935 3.4169520731645546, 10.84937634426605 3.4151876838951822))
double latitude = 40.689234d;
double longitude = -74.044598d;
double diameterInMeters = 2000d; //2km
GeometricShapeFactory shapeFactory = new GeometricShapeFactory();
shapeFactory.setNumPoints(64); // adjustable
shapeFactory.setCentre(new Coordinate(latitude, longitude));
// Length in meters of 1° of latitude = always 111.32 km
shapeFactory.setWidth(diameterInMeters/111320d);
// Length in meters of 1° of longitude = 40075 km * cos( latitude ) / 360
shapeFactory.setHeight(diameterInMeters / (40075000 * Math.cos(Math.toRadians(latitude)) / 360));
Polygon circle = shapeFactory.createEllipse();
GeometricShapeFactory shapeFactory = new GeometricShapeFactory(); shapeFactory.setNumPoints(64);
adjustableshapeFactory.setCentre(new Coordinate(latitude, longitude));
// Length in meters of 1°
I'm new to the GeoTools library for Java and I'm simply trying to draw a polygon on a map. I'm using GPS coordinates for points, which draw just fine, but I just can't figure out how to draw the LineString between them to save my life.
I have checked all the tutorials on geotools.org and also this posting but no avail.
Should this be so complicated? Can anybody maybe post the code fragments required to draw a LineString? This is what I've tried last:
SimpleFeatureType lineType = DataUtilities.createType("LINE", "geom:LineString,name:String");
SimpleFeatureBuilder featureBuilderLines = new SimpleFeatureBuilder(lineType);
SimpleFeatureCollection collectionLines = FeatureCollections.newCollection();
LineString line = builder.createLineString(listOfPoints);
featureBuilderLines.add(line);
SimpleFeature featureLine = featureBuilderLines.buildFeature(null);
((DefaultFeatureCollection)collectionLines).add(featureLine);
Style lineStyle = SLD.createLineStyle(Color.RED, 2.0f);
map.addLayer(new FeatureLayer(collectionLines, lineStyle));
Thanks and advance and best regards
You seem to be mixing Geometry types, try something like:
import org.geotools.geometry.jts.JTSFactoryFinder;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString;
public class TestLineBuilder {
public static void main(String[] args) {
com.vividsolutions.jts.geom.GeometryFactory gFac = JTSFactoryFinder.getGeometryFactory();
Coordinate[] coordinates = new Coordinate[2];
coordinates[0] = new Coordinate(1,3);
coordinates[1] = new Coordinate(3,8);
LineString line =gFac.createLineString(coordinates );
System.out.println(line);
}
}
which gives the right sort of answer for me.
Have been fighting with this. Finally got it to work for saving the map to an image (png) using a mixture of different snippets from the web. Showing the map via JMapFrame.showMap(map); leads to an Exception and crash. Anyway, I needed the image. The example for a line with two points is shown below. Adding a polyline with more points should be identical:
SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
b.setName( "LineFeature" );
//add a geometry property
b.setCRS( DefaultGeographicCRS.WGS84 ); // set crs first
b.add( "line", LineString.class ); // then add geometry
//build the type
final SimpleFeatureType TYPE = b.buildFeatureType();
SimpleFeatureBuilder featureBuilderLines = new SimpleFeatureBuilder(TYPE);
SimpleFeatureCollection collectionLines = new DefaultFeatureCollection("internal",TYPE);
GeometryFactory gFac = JTSFactoryFinder.getGeometryFactory(JTSFactoryFinder.EMPTY_HINTS);
Coordinate[] coordinates = new Coordinate[2];
double latStart = 44.9;
double lonStart = 14.9;
double latEnd = 12.1;
double lonEnd = 9.4;
coordinates[0] = new Coordinate(lonStart, latStart);
coordinates[1] = new Coordinate(lonEnd, latEnd);
LineString line = gFac.createLineString(coordinates );
featureBuilderLines.add(line);
SimpleFeature featureLine = featureBuilderLines.buildFeature(null);
collectionLines.add(featureLine);
float lineWidt = 2.0f;
Style lineStyle = SLD.createLineStyle(Color.red, lineWidth);
SimpleFeatureSource collectionFeatureSource = new CollectionFeatureSource(collectionLines);
map.addLayer(collectionFeatureSource, lineStyle);