/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.model.gocad; import gov.nasa.worldwind.geom.Position; import java.awt.Color; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gdal.osr.CoordinateTransformation; import au.gov.ga.earthsci.worldwind.common.layers.model.gocad.GocadGSurfReader.PositionWithCoord; import au.gov.ga.earthsci.worldwind.common.layers.volume.VolumeLayer; import au.gov.ga.earthsci.worldwind.common.layers.volume.btt.BinaryTriangleTree; import au.gov.ga.earthsci.worldwind.common.render.fastshape.FastShape; import au.gov.ga.earthsci.worldwind.common.util.Validate; /** * {@link GocadReader} implementation for reading GOCAD SGrid files (only * supports single slice, use the {@link VolumeLayer} for volumetric SGrid * files). * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class GocadSGridReader implements GocadReader<FastShape> { public final static String HEADER_REGEX = "(?i).*sgrid.*"; public final static Pattern headerPattern = Pattern.compile("GOCAD\\s+SGrid\\s+.*"); private final static Pattern propAlignmentPattern = Pattern.compile("PROP_ALIGNMENT\\s+(.*?)\\s*"); private final static Pattern asciiDataFilePattern = Pattern.compile("ASCII_DATA_FILE\\s+(.*?)\\s*"); public final static Pattern propertyNamePattern = Pattern.compile("PROPERTY\\s+(\\d+)\\s+\"?(.*?)\"?\\s*"); private final static Pattern propertyNoDataPattern = Pattern .compile("PROP_NO_DATA_VALUE\\s+(\\d+)\\s+([\\d.\\-]+)\\s*"); private String asciiDataFile; private String paintedVariableName; private int paintedVariableId = 1; private boolean cellCentered; private String name; private int xSize; private int ySize; private int zSize; private Float noDataValue; private GocadReaderParameters parameters; @Override public void begin(GocadReaderParameters parameters) { this.parameters = parameters; paintedVariableName = parameters.getPaintedVariable(); } @Override public void addLine(String line) { Matcher matcher = paintedVariablePattern.matcher(line); if (matcher.matches()) { paintedVariableName = matcher.group(1); return; } matcher = namePattern.matcher(line); if (matcher.matches()) { name = matcher.group(1); return; } matcher = axis3Pattern.matcher(line); if (matcher.matches()) { //check for AXIS_N if (matcher.group(1).equals("N")) { xSize = (int) Double.parseDouble(matcher.group(2)); ySize = (int) Double.parseDouble(matcher.group(3)); zSize = (int) Double.parseDouble(matcher.group(4)); } return; } matcher = propAlignmentPattern.matcher(line); if (matcher.matches()) { String propAlignment = matcher.group(1); cellCentered = propAlignment.toLowerCase().equals("cells"); return; } matcher = asciiDataFilePattern.matcher(line); if (matcher.matches()) { asciiDataFile = matcher.group(1); return; } matcher = propertyNamePattern.matcher(line); if (matcher.matches()) { int propertyId = Integer.parseInt(matcher.group(1)); String propertyName = matcher.group(2); if (propertyName.equals(paintedVariableName)) { paintedVariableId = propertyId; } return; } matcher = propertyNoDataPattern.matcher(line); if (matcher.matches()) { int propertyId = Integer.parseInt(matcher.group(1)); float noDataValue = Float.parseFloat(matcher.group(2)); if (propertyId == paintedVariableId) { this.noDataValue = noDataValue; } return; } } @Override public FastShape end(URL context) { if (cellCentered) { xSize--; ySize--; zSize--; } Validate.isTrue(asciiDataFile != null, "Data file not specified"); Validate.isTrue(xSize > 0 && ySize > 0 && zSize > 0, "Volume dimensions are 0"); Validate.isTrue(zSize == 1, "Unsupported AXIS_N z-value: " + zSize + ", only 1 is supported"); List<Position> positions = new ArrayList<Position>(xSize * ySize); float[] values = new float[xSize * ySize]; float minValue = Float.MAX_VALUE; float maxValue = -Float.MAX_VALUE; for (int i = 0; i < values.length; i++) { values[i] = Float.NaN; } InputStream dataInputStream = null; try { URL fileUrl = new URL(context, asciiDataFile); dataInputStream = new BufferedInputStream(fileUrl.openStream()); //TODO add support for SGrid binary property files //setup data variables double[] transformed = new double[3]; CoordinateTransformation transformation = parameters.getCoordinateTransformation(); int positionIndex = 0; double firstXValue = 0, firstYValue = 0; boolean reverseX = false, reverseY = false; //setup the ASCII data file line regex String doublePattern = "([\\d.\\-]+)"; String nonCapturingDoublePattern = "(?:[\\d.\\-]+)"; String spacerPattern = "\\s+"; //regex for coordinates String lineRegex = "\\s*" + doublePattern + spacerPattern + doublePattern + spacerPattern + doublePattern; for (int property = 1; property < paintedVariableId; property++) { //ignore all properties in between coordinates and painted property lineRegex += spacerPattern + nonCapturingDoublePattern; } //only capture the painted property lineRegex += spacerPattern + doublePattern + ".*"; Pattern linePattern = Pattern.compile(lineRegex); //read the ASCII data file BufferedReader reader = new BufferedReader(new InputStreamReader(dataInputStream)); String line; while ((line = reader.readLine()) != null) { Matcher matcher = linePattern.matcher(line); if (matcher.matches()) { double x = Double.parseDouble(matcher.group(1)); double y = Double.parseDouble(matcher.group(2)); double z = Double.parseDouble(matcher.group(3)); float value = Float.parseFloat(matcher.group(4)); //transform the point if (transformation != null) { transformation.TransformPoint(transformed, x, y, z); x = transformed[0]; y = transformed[1]; z = transformed[2]; } //only store the first width*height positions (the rest are evenly spaced at different depths) if (positionIndex < xSize * ySize) { Position position = PositionWithCoord.fromDegrees(y, x, z, positionIndex % xSize, positionIndex / xSize); positions.add(position); } if (positionIndex == 0) { firstXValue = x; firstYValue = y; } else if (positionIndex == 1) { //second x value reverseX = x < firstXValue; } else if (positionIndex == xSize) { //second y value reverseY = y < firstYValue; } //put the data into the float array if (!Float.isNaN(value) && (noDataValue == null || value != noDataValue)) { values[positionIndex] = value; minValue = Math.min(minValue, value); maxValue = Math.max(maxValue, value); } positionIndex++; } } Validate.isTrue(positions.size() == xSize * ySize, "Data file doesn't contain the correct number of positions"); if (reverseX || reverseY) { //if the x-axis or y-axis are reversed, mirror them List<Position> oldPositions = positions; positions = new ArrayList<Position>(oldPositions.size()); for (int y = 0; y < ySize; y++) { int ry = reverseY ? ySize - y - 1 : y; for (int x = 0; x < xSize; x++) { int rx = reverseX ? xSize - x - 1 : x; positions.add(oldPositions.get(rx + ry * xSize)); } } } } catch (Exception e) { e.printStackTrace(); return null; } finally { if (dataInputStream != null) { try { dataInputStream.close(); } catch (IOException e) { } } } BinaryTriangleTree btt = new BinaryTriangleTree(positions, xSize, ySize); btt.setForceGLTriangles(true); //ensures that the shape's triangles can be sorted when transparent FastShape shape = btt.buildMesh(parameters.getMaxVariance()); positions = shape.getPositions(); if (name == null) { name = "SGrid"; } shape.setName(name); shape.setForceSortedPrimitives(true); shape.setLighted(true); shape.setCalculateNormals(true); shape.setTwoSidedLighting(true); //create a color buffer containing a color for each point int colorBufferElementSize = 4; FloatBuffer colorBuffer = FloatBuffer.allocate(positions.size() * colorBufferElementSize); for (Position position : positions) { PositionWithCoord pwv = (PositionWithCoord) position; int u = pwv.u, v = pwv.v; int un = u > 0 ? u - 1 : u, up = u < xSize - 1 ? u + 1 : u, vn = v > 0 ? v - 1 : v, vp = v < ySize - 1 ? v + 1 : v; v *= xSize; vn *= xSize; vp *= xSize; //check all values around the current position for NODATA; if NODATA, use a transparent color float value = values[u + v], l = values[un + v], r = values[up + v], t = values[u + vn], b = values[u + vp], tl = values[un + vn], tr = values[up + vn], bl = values[un + vp], br = values[up + vp]; if (Float.isNaN(value) || Float.isNaN(l) || Float.isNaN(r) || Float.isNaN(t) || Float.isNaN(b) || Float.isNaN(tl) || Float.isNaN(tr) || Float.isNaN(bl) || Float.isNaN(br)) { //this or adjacent cell is NODATA for (int i = 0; i < colorBufferElementSize; i++) { colorBuffer.put(0); } } else { Color color = Color.white; if (parameters.getColorMap() != null) { color = parameters.getColorMap().calculateColorNotingIsValuesPercentages(value, minValue, maxValue); } colorBuffer.put(color.getRed() / 255f).put(color.getGreen() / 255f).put(color.getBlue() / 255f) .put(color.getAlpha() / 255f); } } shape.setColorBuffer(colorBuffer.array()); shape.setColorBufferElementSize(colorBufferElementSize); return shape; } }