// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.data.osm; import java.io.File; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Random; import org.apache.commons.lang.RandomStringUtils; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.gui.layer.OsmDataLayer; /** * This is an utility class that allows you to generate OSM test data. * @author Michael Zangl */ public final class OsmDataGenerator { private static final int DEFAULT_KEY_VALUE_RATIO = 3; private static final int DEFAULT_NODE_COUNT = 1000; private static final String DATA_DIR = "data_nodist" + File.separator + "osmfiles"; private OsmDataGenerator() { // private constructor for utility classes } /** * This is a list of randomly generated strings. * <p> * It provides methods to get them both interned and uninterned. * * @author Michael Zangl */ public static final class RandomStringList { private final Random random; private final String[] strings; private final String[] interned; /** * Creates a new, random List of Strings. * @param seed The seed to use. * @param size The size of the list. */ public RandomStringList(int seed, int size) { random = new SecureRandom(); random.setSeed(seed); strings = new String[size]; interned = new String[size]; for (int i = 0; i < size; i++) { strings[i] = randomString(); interned[i] = strings[i].intern(); } } protected String randomString() { return RandomStringUtils.random(12, 0, 0, true, true, null, random); } /** * Gets a String that was not interned. * @return The String. */ public String get() { int n = random.nextInt(strings.length); return strings[n]; } /** * Gets a String that was interned. * @return The String. */ public String getInterned() { int n = random.nextInt(interned.length); return interned[n]; } } /** * A generator that generates test data by filling a data set. * @author Michael Zangl */ public abstract static class DataGenerator { private String datasetName; protected final Random random; private DataSet ds; /** * Create a new generator. * @param datasetName The name for the generator. Only used for human readability. */ protected DataGenerator(String datasetName) { this.datasetName = datasetName; this.random = new SecureRandom(); } /** * Generates the data set. If this method is called twice, the same dataset is returned. * @return The generated data set. */ public DataSet generateDataSet() { ensureInitialized(); return ds; } protected void ensureInitialized() { if (ds == null) { ds = new DataSet(); fillData(ds); } } protected abstract void fillData(DataSet ds); /** * Create a random node and add it to the dataset. * @param ds the data set * @return a random node */ protected Node createRandomNode(DataSet ds) { Node node = new Node(); node.setEastNorth(new EastNorth(random.nextDouble(), random.nextDouble())); ds.addPrimitive(node); return node; } /** * Gets a file path where this data could be stored. * @return A file path. */ public File getFile() { return new File(DATA_DIR, datasetName + ".osm"); } /** * Creates a new {@link OsmDataLayer} that uses the underlying dataset of this generator. * @return A new data layer. */ public OsmDataLayer createDataLayer() { return new OsmDataLayer(generateDataSet(), datasetName, getFile()); } @Override public String toString() { return "DataGenerator [datasetName=" + datasetName + ']'; } } /** * A data generator that generates a bunch of random nodes. * @author Michael Zangl */ public static class NodeDataGenerator extends DataGenerator { protected final ArrayList<Node> nodes = new ArrayList<>(); private final int nodeCount; protected NodeDataGenerator(String datasetName, int nodeCount) { super(datasetName); this.nodeCount = nodeCount; } @Override public void fillData(DataSet ds) { for (int i = 0; i < nodeCount; i++) { nodes.add(createRandomNode(ds)); } } /** * Gets a random node of this dataset. * @return A random node. */ public Node randomNode() { ensureInitialized(); return nodes.get(random.nextInt(nodes.size())); } } /** * A data generator that generates a bunch of random nodes and fills them with keys/values. * @author Michael Zangl */ public static final class KeyValueDataGenerator extends NodeDataGenerator { private static final int VALUE_COUNT = 200; private static final int KEY_COUNT = 150; private final double tagNodeRation; private RandomStringList keys; private RandomStringList values; private KeyValueDataGenerator(String datasetName, int nodeCount, double tagNodeRation) { super(datasetName, nodeCount); this.tagNodeRation = tagNodeRation; } @Override public void fillData(DataSet ds) { super.fillData(ds); keys = new RandomStringList(random.nextInt(), KEY_COUNT); values = new RandomStringList(random.nextInt(), VALUE_COUNT); double tags = nodes.size() * tagNodeRation; for (int i = 0; i < tags; i++) { String key = keys.get(); String value = values.get(); nodes.get(random.nextInt(nodes.size())).put(key, value); } } /** * Gets the values that were used to fill the tags. * @return The list of strings. */ public RandomStringList randomValues() { ensureInitialized(); return values; } /** * Gets the list of keys that was used to fill the tags. * @return The list of strings. */ public RandomStringList randomKeys() { ensureInitialized(); return keys; } /** * Gets a random value that was used to fill the tags. * @return A random String probably used in as value somewhere. Not interned. */ public String randomValue() { return randomValues().get(); } /** * Gets a random key that was used to fill the tags. * @return A random String probably used in as key somewhere. Not interned. */ public String randomKey() { return randomKeys().get(); } } /** * Generate a generator that creates some nodes and adds random keys and values to it. * @return The generator */ public static KeyValueDataGenerator getKeyValue() { return getKeyValue(DEFAULT_KEY_VALUE_RATIO); } /** * Generate a generator that creates some nodes and adds random keys and values to it. * @param tagNodeRation How many tags to add per node (on average). * @return The generator */ public static KeyValueDataGenerator getKeyValue(double tagNodeRation) { return getKeyValue(DEFAULT_NODE_COUNT, tagNodeRation); } /** * Generate a generator that creates some nodes and adds random keys and values to it. * @param nodeCount The number of nodes the dataset should contain. * @param tagNodeRation How many tags to add per node (on average). * @return The generator */ public static KeyValueDataGenerator getKeyValue(int nodeCount, double tagNodeRation) { return new KeyValueDataGenerator("key-value", nodeCount, tagNodeRation); } /** * Create a generator that generates a bunch of nodes. * @return The generator */ public static DataGenerator getNodes() { return new NodeDataGenerator("nodes", DEFAULT_NODE_COUNT); } }